diff --git a/.package.json.swp b/.package.json.swp deleted file mode 100644 index ca863e2bc32..00000000000 Binary files a/.package.json.swp and /dev/null differ diff --git a/fern.schema.json b/fern.schema.json index 1087d676904..349f5983914 100644 --- a/fern.schema.json +++ b/fern.schema.json @@ -1874,6 +1874,19 @@ } ] }, + "path-parameters": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/service.HttpPathParameterSchema" + } + }, + { + "type": "null" + } + ] + }, "query-parameters": { "oneOf": [ { diff --git a/fern/apis/fern-definition/definition/service.yml b/fern/apis/fern-definition/definition/service.yml index ec62f134f4b..98ce8541c6c 100644 --- a/fern/apis/fern-definition/definition/service.yml +++ b/fern/apis/fern-definition/definition/service.yml @@ -74,6 +74,7 @@ types: - commons.WithDocsSchema properties: content-type: optional + path-parameters: optional> query-parameters: optional> headers: optional> body: optional @@ -191,4 +192,4 @@ types: extends: - commons.WithDocsSchema properties: - error: string \ No newline at end of file + error: string diff --git a/fern/apis/generators-yml/definition/generators.yml b/fern/apis/generators-yml/definition/generators.yml index 72be46b1c56..a188fbc8659 100644 --- a/fern/apis/generators-yml/definition/generators.yml +++ b/fern/apis/generators-yml/definition/generators.yml @@ -138,6 +138,9 @@ types: only-include-referenced-schemas: type: optional docs: Whether to only include schemas referenced by endpoints in the generated SDK (i.e. a form of tree-shaking). Defaults to false. + inline-path-parameters: + type: optional + docs: Whether to include path parameters within the generated in-lined request. Defaults to false. UnionSettingsSchema: enum: @@ -228,6 +231,9 @@ types: only-include-referenced-schemas: type: optional docs: Whether to only include schemas referenced by endpoints in the generated SDK (i.e. a form of tree-shaking). Defaults to false. + inline-path-parameters: + type: optional + docs: Whether to include path parameters within the generated in-lined request. Defaults to false. OpenAPISpecSchema: properties: diff --git a/generators-yml.schema.json b/generators-yml.schema.json index c096f2964a1..fce053dcbd7 100644 --- a/generators-yml.schema.json +++ b/generators-yml.schema.json @@ -653,6 +653,16 @@ "type": "null" } ] + }, + "inline-path-parameters": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1324,6 +1334,16 @@ "type": "null" } ] + }, + "inline-path-parameters": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false diff --git a/generators/go-v2/dynamic-snippets/src/__test__/__snapshots__/ir.test.ts.snap b/generators/go-v2/dynamic-snippets/src/__test__/__snapshots__/ir.test.ts.snap index 2b01bf72475..f119ecbe5a9 100644 --- a/generators/go-v2/dynamic-snippets/src/__test__/__snapshots__/ir.test.ts.snap +++ b/generators/go-v2/dynamic-snippets/src/__test__/__snapshots__/ir.test.ts.snap @@ -4773,6 +4773,63 @@ func do() { " `; +exports[`test definitions > path-parameters 1`] = ` +"package example + +import ( + context "context" + client "github.com/acme/acme-go/client" +) + +func do() { + client := client.NewClient() + client.User.GetOrganization( + context.TODO(), + "organizationId", + ) +} + +------------------------ + +package example + +import ( + context "context" + acme "github.com/acme/acme-go" + client "github.com/acme/acme-go/client" +) + +func do() { + client := client.NewClient() + client.User.GetUser( + context.TODO(), + "userId", + &acme.GetUsersRequest{}, + ) +} + +------------------------ + +package example + +import ( + context "context" + acme "github.com/acme/acme-go" + client "github.com/acme/acme-go/client" +) + +func do() { + client := client.NewClient() + client.User.GetOrganizationUser( + context.TODO(), + "organizationId", + "userId", + &acme.GetOrganizationUserRequest{}, + ) +} +" +`; + exports[`test definitions > plain-text 1`] = `""`; exports[`test definitions > query-parameters 1`] = `""`; diff --git a/package-yml.schema.json b/package-yml.schema.json index 13ef45cc541..ea25e26f1c9 100644 --- a/package-yml.schema.json +++ b/package-yml.schema.json @@ -1894,6 +1894,19 @@ } ] }, + "path-parameters": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/service.HttpPathParameterSchema" + } + }, + { + "type": "null" + } + ] + }, "query-parameters": { "oneOf": [ { diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/operation/convertHttpOperation.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/operation/convertHttpOperation.ts index bf56830347b..c69febdce1d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/operation/convertHttpOperation.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/operation/convertHttpOperation.ts @@ -10,7 +10,7 @@ import { getExamplesFromExtension } from "../../extensions/getExamplesFromExtens import { getFernAvailability } from "../../extensions/getFernAvailability"; import { OperationContext } from "../contexts"; import { convertServer } from "../convertServer"; -import { convertParameters } from "../endpoint/convertParameters"; +import { ConvertedParameters, convertParameters } from "../endpoint/convertParameters"; import { convertRequest } from "../endpoint/convertRequest"; import { convertResponse } from "../endpoint/convertResponse"; @@ -65,7 +65,7 @@ export function convertHttpOperation({ // if request has query params or headers and body is not an object, then use `Body` if ( - (convertedParameters.queryParameters.length > 0 || convertedParameters.headers.length > 0) && + endpointHasNonRequestBodyParameters({ context, convertedParameters }) && convertedRequest != null && convertedRequest.type === "json" && convertedRequest.schema.type !== "object" && @@ -149,3 +149,17 @@ function isEndpointAuthed(operation: OpenAPIV3.OperationObject, document: OpenAP } return false; } + +function endpointHasNonRequestBodyParameters({ + context, + convertedParameters +}: { + context: AbstractOpenAPIV3ParserContext; + convertedParameters: ConvertedParameters; +}): boolean { + return ( + (context.options.inlinePathParameters && convertedParameters.pathParameters.length > 0) || + convertedParameters.queryParameters.length > 0 || + convertedParameters.headers.length > 0 + ); +} diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts index 6dcbbe99103..0a6316c0897 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/options.ts @@ -17,6 +17,8 @@ export interface ParseOpenAPIOptions { respectReadonlySchemas: boolean; /* Whether or not to only include endpoint referenced schemas */ onlyIncludeReferencedSchemas: boolean; + /* Whether or not to include path parameters in the in-lined request */ + inlinePathParameters: boolean; } export const DEFAULT_PARSE_OPENAPI_SETTINGS: ParseOpenAPIOptions = { @@ -27,5 +29,6 @@ export const DEFAULT_PARSE_OPENAPI_SETTINGS: ParseOpenAPIOptions = { optionalAdditionalProperties: true, cooerceEnumsToLiterals: true, respectReadonlySchemas: false, - onlyIncludeReferencedSchemas: false + onlyIncludeReferencedSchemas: false, + inlinePathParameters: false }; diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/parse.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/parse.ts index a0849279306..2f90e6ee199 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/parse.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/parse.ts @@ -31,6 +31,7 @@ export interface SpecImportSettings { objectQueryParameters: boolean; respectReadonlySchemas: boolean; onlyIncludeReferencedSchemas: boolean; + inlinePathParameters: boolean; } export type Source = AsyncAPISource | OpenAPISource | ProtobufSource; @@ -201,7 +202,11 @@ function getParseOptions({ onlyIncludeReferencedSchemas: overrides?.onlyIncludeReferencedSchemas ?? specSettings?.onlyIncludeReferencedSchemas ?? - DEFAULT_PARSE_OPENAPI_SETTINGS.onlyIncludeReferencedSchemas + DEFAULT_PARSE_OPENAPI_SETTINGS.onlyIncludeReferencedSchemas, + inlinePathParameters: + overrides?.inlinePathParameters ?? + specSettings?.inlinePathParameters ?? + DEFAULT_PARSE_OPENAPI_SETTINGS.inlinePathParameters }; } diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/anyOf.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/anyOf.json index d6d5a2b41e9..b0be7e7f005 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/anyOf.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/anyOf.json @@ -38,6 +38,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/apiture.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/apiture.json index a7c4b44e38f..813246030a3 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/apiture.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/apiture.json @@ -14850,6 +14850,7 @@ The authenticated user must have the `account.allows.manageJointOwners` permissi "content-type": "application/json", "headers": undefined, "name": "NewJointOwnerInvitation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15121,6 +15122,7 @@ The user must have the `allows.manageOverdraftAccounts` permission on the accoun "content-type": "application/json", "headers": undefined, "name": "OverdraftProtectionPatch", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15987,6 +15989,7 @@ Note: This operation requires an identity challenge if the financial institution }, }, "name": "NewTransfer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16330,6 +16333,7 @@ Note: This operation requires an identity challenge if the financial institution }, }, "name": "TransferPatch", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/aries.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/aries.json index 57ac850af35..a2ba8aa0d7c 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/aries.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/aries.json @@ -11463,6 +11463,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "PerformRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11550,6 +11551,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SendMenu", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11766,6 +11768,7 @@ docs: Menu interaction over connection "content-type": "application/json", "headers": undefined, "name": "SendMessage", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12068,6 +12071,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "CreateInvitationRequest", + "path-parameters": undefined, "query-parameters": { "alias": { "docs": "Alias", @@ -12205,6 +12209,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "ConnectionStaticRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12607,6 +12612,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "ReceiveInvitationRequest", + "path-parameters": undefined, "query-parameters": { "alias": { "docs": "Alias", @@ -12674,6 +12680,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "ConnectionMetadataSetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13706,6 +13713,7 @@ docs: Connection management "content-type": "application/json", "headers": undefined, "name": "CredentialDefinitionSendRequest", + "path-parameters": undefined, "query-parameters": { "conn_id": { "docs": "Connection identifier", @@ -14509,6 +14517,7 @@ docs: Credential definition operations "content-type": "application/json", "headers": undefined, "name": "W3CCredentialsListRequest", + "path-parameters": undefined, "query-parameters": { "count": { "docs": "Maximum number to retrieve", @@ -15238,6 +15247,7 @@ docs: Holder credential management "content-type": "application/json", "headers": undefined, "name": "DIDXRequest", + "path-parameters": undefined, "query-parameters": { "alias": { "docs": "Alias for connection", @@ -16236,6 +16246,7 @@ docs: Feature discovery v2 "content-type": "application/json", "headers": undefined, "name": "Date", + "path-parameters": undefined, "query-parameters": { "endorser_write_txn": { "docs": "Endorser will write the transaction after endorsing it", @@ -17483,6 +17494,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17697,6 +17709,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialConnFreeOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18385,6 +18398,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialIssueRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18432,6 +18446,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18608,6 +18623,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialBoundOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19039,6 +19055,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialProposalRequestMand", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19264,6 +19281,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialFreeOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19533,6 +19551,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialProposalRequestOpt", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19711,6 +19730,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialStoreRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21848,6 +21868,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20IssueCredSchemaCore", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22112,6 +22133,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredOfferConnFreeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23116,6 +23138,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredIssueRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23163,6 +23186,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredIssueProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23417,6 +23441,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredBoundOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23655,6 +23680,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24171,6 +24197,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24696,6 +24723,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredRequestFree", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24953,6 +24981,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredStoreRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27468,6 +27497,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SignRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27517,6 +27547,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "VerifyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27656,6 +27687,7 @@ docs: Sign and verify json-ld data "content-type": "application/json", "headers": undefined, "name": "TAAAccept", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28505,6 +28537,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "AdminMediationDeny", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28876,6 +28909,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "MediationCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28929,6 +28963,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "KeylistQueryFilterRequest", + "path-parameters": undefined, "query-parameters": { "paginate_limit": { "docs": "limit number of results", @@ -29007,6 +29042,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "KeylistUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29125,6 +29161,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "MediationIdMatchInfo", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29804,6 +29841,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateWalletRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29854,6 +29892,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "RemoveWalletRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29901,6 +29940,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateWalletTokenRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30058,6 +30098,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateWalletRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30602,6 +30643,7 @@ docs: Multitenant wallet management "content-type": "application/json", "headers": undefined, "name": "InvitationCreateRequest", + "path-parameters": undefined, "query-parameters": { "auto_accept": { "docs": "Auto-accept connection (defaults to configuration)", @@ -31394,6 +31436,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationCreateRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32031,6 +32074,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32271,6 +32315,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationSendRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32508,6 +32553,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationProposalRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32898,6 +32944,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationSendRequestToProposal", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34613,6 +34660,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresCreateRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35212,6 +35260,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35428,6 +35477,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresSendRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35645,6 +35695,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresProposalRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35832,6 +35883,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresSpecByFormatRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -36000,6 +36052,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresentationSendRequestToProposal", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37576,6 +37629,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "ClearPendingRevocationsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37672,6 +37726,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "RevRegCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38512,6 +38567,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "RevokeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38724,6 +38780,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "RevRegUpdateTailsFileUri", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -39920,6 +39977,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SchemaSendRequest", + "path-parameters": undefined, "query-parameters": { "conn_id": { "docs": "Connection identifier", @@ -40520,6 +40578,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PingRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40635,6 +40694,7 @@ docs: Trust-ping over connection "content-type": "application/json", "headers": undefined, "name": "DIDCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40889,6 +40949,7 @@ docs: Trust-ping over connection "content-type": "application/json", "headers": undefined, "name": "DIDEndpointWithType", + "path-parameters": undefined, "query-parameters": { "conn_id": { "docs": "Connection identifier", diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/assembly.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/assembly.json index 9ec9d6991dd..ceec06cc76d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/assembly.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/assembly.json @@ -5123,6 +5123,7 @@ The LLM response data, as well as any context provided in the original request w "content-type": "application/json", "headers": undefined, "name": "LemurQuestionAnswerParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5180,6 +5181,7 @@ The LLM response data, as well as any context provided in the original request w "content-type": "application/json", "headers": undefined, "name": "LemurSummaryParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5237,6 +5239,7 @@ The LLM response data, as well as any context provided in the original request w "content-type": "application/json", "headers": undefined, "name": "LemurTaskParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5522,6 +5525,7 @@ docs: LeMUR related operations "content-type": "application/json", "headers": undefined, "name": "CreateRealtimeTemporaryTokenParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5809,6 +5813,7 @@ docs: Real-time transcription "content-type": "application/json", "headers": undefined, "name": "CreateTranscriptParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/availability.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/availability.json index 66577281dc5..c64a46f497d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/availability.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/availability.json @@ -124,6 +124,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostSuccessInlineBetaBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -160,6 +161,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostSuccessInlineObjectDeprecationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -199,6 +201,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostSuccessInlinePropertyDeprecationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -238,6 +241,7 @@ "content-type": "application/json", "headers": undefined, "name": "Settings", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/axle.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/axle.json index 5ee10880330..e30fe4b42d1 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/axle.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/axle.json @@ -921,6 +921,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "StartIgnitionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1335,6 +1336,7 @@ Auth codes are ephemeral and expire after 10 minutes, while accessTokens do not "content-type": "application/json", "headers": undefined, "name": "ExchangeTokenRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/belvo.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/belvo.json index 9d3bf0b1214..e73e25ef8e9 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/belvo.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/belvo.json @@ -36577,6 +36577,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "BalancesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -38801,6 +38802,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CategorizationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40161,6 +40163,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "EmploymentRecordRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -40645,6 +40648,7 @@ docs: "# Employment Records\n\nOur employment records\_resource lets you get a c "content-type": "application/json", "headers": undefined, "name": "EyodIncomeVerificationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41720,6 +41724,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "IncomesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -48789,6 +48794,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "InvoicesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -52292,6 +52298,7 @@ For single links, you have to perform POST calls to an institution **every time* "content-type": "application/json", "headers": undefined, "name": "ChangeAccessMode", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -53237,6 +53244,7 @@ For a list of standards codes, see the table below. "content-type": "application/json", "headers": undefined, "name": "LinksRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -53468,6 +53476,7 @@ Belvo supports a base64 encoded `private_key`. If the `private_key` parameter is "content-type": "application/json", "headers": undefined, "name": "LinksPutRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -56967,6 +56976,7 @@ A payment intent captures all payment information (such as the amount to be char "content-type": "application/json", "headers": undefined, "name": "CreatePaymentIntentPse", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -57474,6 +57484,7 @@ During the payment intent flow, you need to fill in the payment intent with requ "content-type": "application/json", "headers": undefined, "name": "PatchPaymentIntentPse", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -59606,6 +59617,7 @@ docs: > "content-type": "application/json", "headers": undefined, "name": "PaymentWebhookRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -60198,6 +60210,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "ReceivableTransactionRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -61590,6 +61603,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "RecurringExpensesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -65420,6 +65434,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxComplianceStatusRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -66932,6 +66947,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxDeclarationsRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -68233,6 +68249,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxRetentionsRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -78619,6 +78636,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxStatusRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/content-type.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/content-type.json index aab8b728460..bcd94e25c9a 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/content-type.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/content-type.json @@ -56,6 +56,7 @@ "content-type": "multipart/form-data", "headers": undefined, "name": "PostTestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/deel.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/deel.json index cdbf722cbf4..29c25191d2f 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/deel.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/deel.json @@ -20835,6 +20835,7 @@ docs: End-points to retrieve paid invoices and reciepts. "content-type": "application/json", "headers": undefined, "name": "InputToCreateFileRef", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21297,6 +21298,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PremiumToAddContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21358,6 +21360,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToAmendDetailsContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21528,6 +21531,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21695,6 +21699,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerOngoingTimeBased", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21860,6 +21865,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerPaygMilestones", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22025,6 +22031,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerPaygTasks", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22192,6 +22199,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerPayAsYouGoTimeBased", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22355,6 +22363,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToTerminateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23284,6 +23293,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "docs": "This is the file you will upload in a multi-part form.", "headers": undefined, "name": "AddContractDocumentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23392,6 +23402,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "InputToPatchContractExternalId", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23525,6 +23536,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "docs": "This is the file you will upload in a multi-part form.", "headers": undefined, "name": "EditContractDocumentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23937,6 +23949,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "ContractInvitationToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24006,6 +24019,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "EstimateFirstPaymentContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24069,6 +24083,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "ContractSignatureToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24894,6 +24909,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "EorContractToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25427,6 +25443,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25490,6 +25507,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentReviewToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25547,6 +25565,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentReviewsToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25951,6 +25970,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentToUpdateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26924,6 +26944,7 @@ docs: Helper end-points for choosing options for other operations "content-type": "application/json", "headers": undefined, "name": "MilestoneToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26992,6 +27013,7 @@ docs: Helper end-points for choosing options for other operations "content-type": "application/json", "headers": undefined, "name": "MilestoneReviewToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27058,6 +27080,7 @@ docs: Helper end-points for choosing options for other operations "content-type": "application/json", "headers": undefined, "name": "MilestoneReviewsToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27592,6 +27615,7 @@ docs: Helper end-points for managing milestones of Deel contracts "content-type": "application/json", "headers": undefined, "name": "OffCyclePaymentToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28326,6 +28350,7 @@ docs: Helper end-points for additional information about your organizations "content-type": "application/json", "headers": undefined, "name": "InputToCreatePgoTask", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28403,6 +28428,7 @@ docs: Helper end-points for additional information about your organizations "content-type": "application/json", "headers": undefined, "name": "RequestBodyToCreatePgoTaskReviewsReviewsContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28481,6 +28507,7 @@ docs: Helper end-points for additional information about your organizations "content-type": "application/json", "headers": undefined, "name": "RequestBodyToCreatePgoTaskReviewsByIdReviewsContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29498,6 +29525,7 @@ docs: End-points to manage tasks for a given contract "content-type": "application/json", "headers": undefined, "name": "TimeoffToReviewContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30062,6 +30090,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30125,6 +30154,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetReviewToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30182,6 +30212,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetReviewsToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30637,6 +30668,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetToUpdateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31265,6 +31297,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "PatchWebhookRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31417,6 +31450,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateWebhookRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/devrev.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/devrev.json index 1120e3ffe14..a6f53eb9b5e 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/devrev.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/devrev.json @@ -10409,6 +10409,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ArtifactsPrepareRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10607,6 +10608,7 @@ connections can not be deleted using this method. "content-type": "application/json", "headers": undefined, "name": "DevOrgAuthConnectionsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -10752,6 +10754,7 @@ disabled. "content-type": "application/json", "headers": undefined, "name": "DevOrgAuthConnectionsToggleRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -11130,6 +11133,7 @@ requesting an application access token (AAT). "content-type": "application/json", "headers": undefined, "name": "AuthTokensCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11177,6 +11181,7 @@ JTI claim of the token in the authorization header. "content-type": "application/json", "headers": undefined, "name": "AuthTokensDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -11385,6 +11390,7 @@ authenticated user. "content-type": "application/json", "headers": undefined, "name": "AuthTokensSelfDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -11477,6 +11483,7 @@ organization. "content-type": "application/json", "headers": undefined, "name": "AuthTokensUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12329,6 +12336,7 @@ docs: Dev user interactions. "content-type": "application/json", "headers": undefined, "name": "PartsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13081,6 +13089,7 @@ records. "content-type": "application/json", "headers": undefined, "name": "RevOrgsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13130,6 +13139,7 @@ records. "content-type": "application/json", "headers": undefined, "name": "RevOrgsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13432,6 +13442,7 @@ records. "content-type": "application/json", "headers": undefined, "name": "RevOrgsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13905,6 +13916,7 @@ permitted. "content-type": "application/json", "headers": undefined, "name": "TagsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13954,6 +13966,7 @@ permitted. "content-type": "application/json", "headers": undefined, "name": "TagsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14202,6 +14215,7 @@ tags. "content-type": "application/json", "headers": undefined, "name": "TagsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15229,6 +15243,7 @@ response. "content-type": "application/json", "headers": undefined, "name": "WebhooksCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15278,6 +15293,7 @@ response. "content-type": "application/json", "headers": undefined, "name": "WebhooksDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15521,6 +15537,7 @@ won't receive any object events until successfully verified. "content-type": "application/json", "headers": undefined, "name": "WebhooksUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16025,6 +16042,7 @@ docs: Webhook event APIs. "content-type": "application/json", "headers": undefined, "name": "WorksDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/discriminated-union-value-title.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/discriminated-union-value-title.json index 3444dc5d076..5303ec20f77 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/discriminated-union-value-title.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/discriminated-union-value-title.json @@ -38,6 +38,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/file-upload.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/file-upload.json index 5129ba9cb5f..e5e600efba6 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/file-upload.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/file-upload.json @@ -40,6 +40,7 @@ "content-type": "multipart/form-data", "headers": undefined, "name": "UploadFileRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flagright.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flagright.json index 8dfa180604f..f6177b841cc 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flagright.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flagright.json @@ -1300,6 +1300,7 @@ In order to make individual events retrievable, you also need to pass in a uniqu "content-type": "application/json", "headers": undefined, "name": "BusinessUserEvent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1833,6 +1834,7 @@ In order to make individual events retrievable, you also need to pass in a uniqu "content-type": "application/json", "headers": undefined, "name": "TransactionEvent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2074,6 +2076,7 @@ In order to make individual events retrievable, you also need to pass in a uniqu "content-type": "application/json", "headers": undefined, "name": "ConsumerUserEvent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flexport.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flexport.json index 3a2bb694ed4..cd360552ce7 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flexport.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/flexport.json @@ -11033,6 +11033,7 @@ errors: "content-type": "application/json", "headers": undefined, "name": "CreateBooking", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12721,6 +12722,7 @@ docs: Endpoints relating to Booking objects "content-type": "application/json", "headers": undefined, "name": "CreateBookingAmendment", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13092,6 +13094,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateBookingLineItem", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13678,6 +13681,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateCarbonCalculation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14131,6 +14135,7 @@ docs: Endpoints relating to Carbon Calculations "content-type": "application/json", "headers": undefined, "name": "CreateCommercialInvoice", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14629,6 +14634,7 @@ docs: Endpoints relating to Carbon Calculations "content-type": "application/json", "headers": undefined, "name": "UpdateCommercialInvoice", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15398,6 +15404,7 @@ docs: Endpoints relating to Commercial Invoice objects "content-type": "application/json", "headers": undefined, "name": "CreateCompany", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15801,6 +15808,7 @@ docs: Endpoints relating to Commercial Invoice objects "content-type": "application/json", "headers": undefined, "name": "UpdateCompany", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16523,6 +16531,7 @@ docs: Endpoints relating to Company objects "content-type": "application/json", "headers": undefined, "name": "CreateCompanyEntity", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16677,6 +16686,7 @@ docs: Endpoints relating to Company objects "content-type": "application/json", "headers": undefined, "name": "UpdateCompanyEntity", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17154,6 +17164,7 @@ docs: Endpoints relating to CompanyEntity objects "content-type": "application/json", "headers": undefined, "name": "CreateContact", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17390,6 +17401,7 @@ docs: Endpoints relating to CompanyEntity objects "content-type": "application/json", "headers": undefined, "name": "UpdateContact", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21479,6 +21491,7 @@ docs: Endpoints relating to Invoice objects "content-type": "application/json", "headers": undefined, "name": "CreateLocation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21655,6 +21668,7 @@ docs: Endpoints relating to Invoice objects "content-type": "application/json", "headers": undefined, "name": "UpdateLocation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22538,6 +22552,7 @@ docs: Endpoints relating to Ports objects "content-type": "application/json", "headers": undefined, "name": "CreateProduct", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22819,6 +22834,7 @@ docs: Endpoints relating to Ports objects "content-type": "application/json", "headers": undefined, "name": "UpdateProduct", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26478,6 +26494,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "ShipmentsShareableRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27162,6 +27179,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpdateShipment", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/float.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/float.json index 7d5966c4d3b..3f9e90b448d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/float.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/float.json @@ -35,6 +35,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hathora.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hathora.json index 3dd30c3ec71..4ded7d5271e 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hathora.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hathora.json @@ -2722,6 +2722,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LoginGoogleRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2768,6 +2769,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LoginNicknameRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3117,6 +3119,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "RunBuildRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4028,6 +4031,7 @@ service: }, }, "name": "CreateLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -4105,6 +4109,7 @@ service: }, }, "name": "CreateLocalLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -4182,6 +4187,7 @@ service: }, }, "name": "CreatePrivateLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -4259,6 +4265,7 @@ service: }, }, "name": "CreatePublicLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -4419,6 +4426,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SetLobbyStateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5441,6 +5449,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateRoomRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hookdeck.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hookdeck.json index 8f5e82eca86..3027d419245 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hookdeck.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hookdeck.json @@ -8230,6 +8230,7 @@ docs: An attempt is any request that Hookdeck makes on behalf of an event. "content-type": "application/json", "headers": undefined, "name": "CreateBookmarkRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8501,6 +8502,7 @@ docs: An attempt is any request that Hookdeck makes on behalf of an event. "content-type": "application/json", "headers": undefined, "name": "TriggerBookmarkRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8603,6 +8605,7 @@ docs: An attempt is any request that Hookdeck makes on behalf of an event. "content-type": "application/json", "headers": undefined, "name": "UpdateBookmarkRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9091,6 +9094,7 @@ docs: A bookmark lets you conveniently store and replay a specific request. "content-type": "application/json", "headers": undefined, "name": "CreateEventBulkRetryRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10513,6 +10517,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "CreateIgnoredEventBulkRetryRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11129,6 +11134,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "CreateRequestBulkRetryRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12441,6 +12447,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "CreateConnectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13099,6 +13106,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "UpdateConnectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13260,6 +13268,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "UpsertConnectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14524,6 +14533,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateDestinationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14777,6 +14787,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpdateDestinationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14855,6 +14866,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpsertDestinationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16137,6 +16149,7 @@ docs: An event is any request that Hookdeck receives from a source. "content-type": "application/json", "headers": undefined, "name": "CreateIntegrationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16394,6 +16407,7 @@ docs: An event is any request that Hookdeck receives from a source. "content-type": "application/json", "headers": undefined, "name": "UpdateIntegrationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16820,6 +16834,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateIssueTriggerRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17144,6 +17159,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateIssueTriggerRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17218,6 +17234,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpsertIssueTriggerRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18087,6 +18104,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpdateIssueRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18606,6 +18624,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "ToggleWebhookNotificationsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19219,6 +19238,7 @@ docs: Notifications let your team receive alerts anytime an issue changes. "content-type": "application/json", "headers": undefined, "name": "RetryRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19826,6 +19846,7 @@ docs: A request represent a webhook received by Hookdeck. "content-type": "application/json", "headers": undefined, "name": "CreateRulesetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20068,6 +20089,7 @@ docs: A request represent a webhook received by Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpdateRulesetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20136,6 +20158,7 @@ docs: A request represent a webhook received by Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpsertRulesetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20581,6 +20604,7 @@ docs: A ruleset defines a group of rules that can be used across many connection "content-type": "application/json", "headers": undefined, "name": "CreateSourceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20831,6 +20855,7 @@ docs: A ruleset defines a group of rules that can be used across many connection "content-type": "application/json", "headers": undefined, "name": "UpdateSourceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20897,6 +20922,7 @@ docs: A ruleset defines a group of rules that can be used across many connection "content-type": "application/json", "headers": undefined, "name": "UpsertSourceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21315,6 +21341,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "CreateTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21666,6 +21693,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "TestTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21740,6 +21768,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpdateTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21810,6 +21839,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpsertTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/humanloop.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/humanloop.json index ea63276cc26..5e200c13003 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/humanloop.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/humanloop.json @@ -7184,6 +7184,7 @@ If `"add"` or `"remove"`, one of the `version_id` or `environment` query paramet "content-type": "application/json", "headers": undefined, "name": "DatasetRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag to identify a deployed Version to base the created Version on. Only used when `action` is `"add"` or `"remove"`.", @@ -7768,6 +7769,7 @@ By default the deployed version of the Dataset is returned. Use the query parame "content-type": "application/json", "headers": undefined, "name": "UpdateDatasetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9209,6 +9211,7 @@ as completed.", "content-type": "application/json", "headers": undefined, "name": "BodyEvaluationsUpdateStatus", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10009,6 +10012,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "EvaluatorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10271,6 +10275,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "RunSyncEvaluationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10807,6 +10812,7 @@ By default the deployed version of the Evaluator is returned. Use the query para "content-type": "application/json", "headers": undefined, "name": "UpdateEvaluatorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12540,6 +12546,7 @@ in the case where you are storing or deriving your Prompt details in code.", "content-type": "application/json", "headers": undefined, "name": "PromptCallRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag of the deployed version to log to.", @@ -12853,6 +12860,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "PromptRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13530,6 +13538,7 @@ in the case where you are storing or deriving your Prompt details in code.", "content-type": "application/json", "headers": undefined, "name": "PromptLogRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag of the deployed version to log to.", @@ -13659,6 +13668,7 @@ in the case where you are storing or deriving your Prompt details in code.", "content-type": "application/json", "headers": undefined, "name": "UpdatePromptRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15843,6 +15853,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "ToolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16433,6 +16444,7 @@ in the case where you are storing or deriving your Tool details in code.", "content-type": "application/json", "headers": undefined, "name": "ToolLogRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag of the deployed version to log to.", @@ -16538,6 +16550,7 @@ in the case where you are storing or deriving your Tool details in code.", "content-type": "application/json", "headers": undefined, "name": "UpdateToolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hume.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hume.json index 27e80d62e42..74ab8cf13fa 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hume.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/hume.json @@ -179,6 +179,7 @@ If you wish to supply more than 100 URLs, consider providing them as an archive "content-type": "application/json; charset=utf-8", "headers": undefined, "name": "BaseRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/inline-path-parameters.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/inline-path-parameters.json new file mode 100644 index 00000000000..a47eed7d1b8 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/inline-path-parameters.json @@ -0,0 +1,297 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "GetOrganizationUser": { + "auth": false, + "docs": "Returns the user with the provided user ID.", + "examples": [ + { + "path-parameters": { + "organization_id": "organization_id", + "user_id": "user_id", + }, + "query-parameters": { + "limit": 1, + }, + "response": { + "body": { + "results": [ + "results", + ], + }, + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/organizations/{organization_id}/users/{user_id}", + "request": { + "name": "GetOrganizationUserRequest", + "path-parameters": { + "organization_id": "string", + "user_id": "string", + }, + "query-parameters": { + "limit": "integer", + }, + }, + "response": { + "docs": "Successful response", + "type": "GetOrganizationUserResponse", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "GetUser": { + "auth": false, + "docs": "Returns the user with the provided user ID.", + "examples": [ + { + "path-parameters": { + "user_id": "user_id", + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/users/{user_id}", + "request": { + "name": "GetUserRequest", + "path-parameters": { + "user_id": "string", + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "GetOrganizationUserResponse": { + "docs": undefined, + "properties": { + "results": "optional>", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "service: + auth: false + base-path: '' + endpoints: + GetUser: + path: /users/{user_id} + method: GET + auth: false + docs: Returns the user with the provided user ID. + source: + openapi: ../openapi.yml + request: + name: GetUserRequest + path-parameters: + user_id: string + examples: + - path-parameters: + user_id: user_id + GetOrganizationUser: + path: /organizations/{organization_id}/users/{user_id} + method: GET + auth: false + docs: Returns the user with the provided user ID. + source: + openapi: ../openapi.yml + request: + name: GetOrganizationUserRequest + path-parameters: + organization_id: string + user_id: string + query-parameters: + limit: integer + response: + docs: Successful response + type: GetOrganizationUserResponse + examples: + - path-parameters: + organization_id: organization_id + user_id: user_id + query-parameters: + limit: 1 + response: + body: + results: + - results + source: + openapi: ../openapi.yml +types: + GetOrganizationUserResponse: + properties: + results: optional> + source: + openapi: ../openapi.yml +", + }, + "organizations.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "Search": { + "auth": false, + "display-name": "Search an organization", + "docs": "", + "examples": [ + { + "path-parameters": { + "id": "id", + }, + "request": {}, + "response": { + "body": { + "results": [ + "results", + ], + }, + }, + }, + ], + "method": "POST", + "pagination": undefined, + "path": "/organizations/{organization_id}/search", + "request": { + "body": { + "properties": { + "access": { + "docs": "Access level", + "type": "optional", + }, + }, + }, + "content-type": "application/json", + "headers": undefined, + "name": "SearchRequest", + "path-parameters": { + "id": "string", + }, + "query-parameters": undefined, + }, + "response": { + "docs": "Successful response", + "type": "SearchResponse", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "SearchRequestAccess": { + "docs": "Access level", + "enum": [ + "private", + "public", + ], + "source": { + "openapi": "../openapi.yml", + }, + }, + "SearchResponse": { + "docs": undefined, + "properties": { + "results": "optional>", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "types: + SearchRequestAccess: + enum: + - private + - public + docs: Access level + source: + openapi: ../openapi.yml + SearchResponse: + properties: + results: optional> + source: + openapi: ../openapi.yml +service: + auth: false + base-path: '' + endpoints: + Search: + path: /organizations/{organization_id}/search + method: POST + auth: false + docs: '' + source: + openapi: ../openapi.yml + display-name: Search an organization + request: + name: SearchRequest + path-parameters: + id: string + body: + properties: + access: + type: optional + docs: Access level + content-type: application/json + response: + docs: Successful response + type: SearchResponse + examples: + - path-parameters: + id: id + request: {} + response: + body: + results: + - results + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "Inlined path parameters", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: Inlined path parameters +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/intercom.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/intercom.json index 84317170678..f274f4bfc5f 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/intercom.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/intercom.json @@ -13840,6 +13840,7 @@ You can view the currently authorised admin along with the embedded app object ( "content-type": "application/json", "headers": undefined, "name": "SetAwayAdminRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20205,6 +20206,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AttachContactToACompanyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22291,6 +22293,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "MergeContactsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22862,6 +22865,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25041,6 +25045,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "AttachContactToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25555,6 +25560,7 @@ It is not possible to use this endpoint with Workflows. "content-type": "application/json", "headers": undefined, "name": "ConvertConversationToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25644,6 +25650,7 @@ This will return the Message model that has been created. "content-type": "application/json", "headers": undefined, "name": "CreateConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26389,6 +26396,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "DetachContactFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29714,6 +29722,7 @@ If you want to reply to a coveration or take an action such as assign, unassign, "content-type": "application/json", "headers": undefined, "name": "UpdateConversationRequest", + "path-parameters": undefined, "query-parameters": { "display_as": { "docs": "Set to plaintext to retrieve conversation messages in plain text.", @@ -34611,6 +34620,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35237,6 +35247,7 @@ You can update a data attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -36352,6 +36363,7 @@ Duplicated events are responded to using the normal `202 Accepted` code - an err "content-type": "application/json", "headers": undefined, "name": "CreateDataEventSummariesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -36950,6 +36962,7 @@ The only parameters you need to provide are the range of dates that you want exp "content-type": "application/json", "headers": undefined, "name": "CreateDataExportsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37760,6 +37773,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38670,6 +38684,7 @@ Collections will be returned in descending order on the `updated_at` attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41780,6 +41795,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateNoteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -42775,6 +42791,7 @@ This will return a subscription type model for the subscription type that was ad "content-type": "application/json", "headers": undefined, "name": "AttachSubscriptionTypeToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43526,6 +43543,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43615,6 +43633,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43704,6 +43723,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -44015,6 +44035,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -44126,6 +44147,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -45220,6 +45242,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -45337,6 +45360,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -46289,6 +46313,7 @@ docs: Everything about your ticket types "content-type": "application/json", "headers": undefined, "name": "CreateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -47625,6 +47650,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -49694,6 +49720,7 @@ docs: Everything about your tickets "content-type": "application/json", "headers": undefined, "name": "ConvertVisitorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/merge.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/merge.json index ac78aea9b6e..95f50fc2068 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/merge.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/merge.json @@ -19452,6 +19452,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LinkedAccountSelectiveSyncConfigurationListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/oauth.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/oauth.json index b7c0177bbcc..5fa4d286016 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/oauth.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/oauth.json @@ -99,6 +99,7 @@ "content-type": "application/json", "headers": undefined, "name": "AuthGetTokenRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/only-include-referenced-schemas.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/only-include-referenced-schemas.json index bf027306997..1af8e788077 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/only-include-referenced-schemas.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/only-include-referenced-schemas.json @@ -13333,6 +13333,7 @@ You can view the currently authorised admin along with the embedded app object ( "content-type": "application/json", "headers": undefined, "name": "SetAwayAdminRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19698,6 +19699,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AttachContactToACompanyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21784,6 +21786,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "MergeContactsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22355,6 +22358,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24534,6 +24538,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "AttachContactToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25048,6 +25053,7 @@ It is not possible to use this endpoint with Workflows. "content-type": "application/json", "headers": undefined, "name": "ConvertConversationToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25137,6 +25143,7 @@ This will return the Message model that has been created. "content-type": "application/json", "headers": undefined, "name": "CreateConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25882,6 +25889,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "DetachContactFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29207,6 +29215,7 @@ If you want to reply to a coveration or take an action such as assign, unassign, "content-type": "application/json", "headers": undefined, "name": "UpdateConversationRequest", + "path-parameters": undefined, "query-parameters": { "display_as": { "docs": "Set to plaintext to retrieve conversation messages in plain text.", @@ -34104,6 +34113,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34730,6 +34740,7 @@ You can update a data attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35845,6 +35856,7 @@ Duplicated events are responded to using the normal `202 Accepted` code - an err "content-type": "application/json", "headers": undefined, "name": "CreateDataEventSummariesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -36368,6 +36380,7 @@ The only parameters you need to provide are the range of dates that you want exp "content-type": "application/json", "headers": undefined, "name": "CreateDataExportsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37178,6 +37191,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38088,6 +38102,7 @@ Collections will be returned in descending order on the `updated_at` attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41198,6 +41213,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateNoteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -42193,6 +42209,7 @@ This will return a subscription type model for the subscription type that was ad "content-type": "application/json", "headers": undefined, "name": "AttachSubscriptionTypeToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -42944,6 +42961,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43033,6 +43051,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43122,6 +43141,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43433,6 +43453,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43544,6 +43565,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -44638,6 +44660,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -44755,6 +44778,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -45707,6 +45731,7 @@ docs: Everything about your ticket types "content-type": "application/json", "headers": undefined, "name": "CreateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -47043,6 +47068,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -49112,6 +49138,7 @@ docs: Everything about your tickets "content-type": "application/json", "headers": undefined, "name": "ConvertVisitorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/permit.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/permit.json index b77a3814703..a3fa41b127e 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/permit.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/permit.json @@ -6241,6 +6241,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "TenantCreateBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6311,6 +6312,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserCreateBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6368,6 +6370,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "TenantDeleteBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6425,6 +6428,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserDeleteBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6495,6 +6499,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserReplaceBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6845,6 +6850,7 @@ If the permission is already granted, it is skipped.", "content-type": "application/json", "headers": undefined, "name": "ConditionSetRuleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7014,6 +7020,7 @@ If the permission is not granted, it is skipped.", "content-type": "application/json", "headers": undefined, "name": "ConditionSetRuleRemove", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -7487,6 +7494,7 @@ If we check the checkbox where `us_based_employees` and `private_repos->clone` a "content-type": "application/json", "headers": undefined, "name": "ConditionSetCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8244,6 +8252,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ConditionSetUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9137,6 +9146,7 @@ allowed.", "content-type": "application/json", "headers": undefined, "name": "EnvironmentCopy", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9593,6 +9603,7 @@ allowed.", "content-type": "application/json", "headers": undefined, "name": "EnvironmentUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10170,6 +10181,7 @@ authenticated actor (i.e: human team member or api key).", "content-type": "application/json", "headers": undefined, "name": "OrganizationCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10412,6 +10424,7 @@ authenticated actor (i.e: human team member or api key).", "content-type": "application/json", "headers": undefined, "name": "OrganizationUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10775,6 +10788,7 @@ Every project is a separate silo, and has its own unique set of environments and "content-type": "application/json", "headers": undefined, "name": "ProjectCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10996,6 +11010,7 @@ Every project is a separate silo, and has its own unique set of environments and "content-type": "application/json", "headers": undefined, "name": "ProjectUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11361,6 +11376,7 @@ to a role as one action. "content-type": "application/json", "headers": undefined, "name": "ResourceActionGroupCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11656,6 +11672,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceActionGroupUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12124,6 +12141,7 @@ Each (resource, action) pair defines a unique permission level. "content-type": "application/json", "headers": undefined, "name": "ResourceActionCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12409,6 +12427,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceActionUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13576,6 +13595,7 @@ and will return the existing instance object in the response body.", "content-type": "application/json", "headers": undefined, "name": "ResourceInstanceCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13838,6 +13858,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceInstanceUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14375,6 +14396,7 @@ If some of the permissions specified are already assigned, will skip them.", "content-type": "application/json", "headers": undefined, "name": "ResourceRoleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14914,6 +14936,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15749,6 +15772,7 @@ A resource may also contain: "content-type": "application/json", "headers": undefined, "name": "ResourceCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16160,6 +16184,7 @@ TODO: we need to decide if we are auto-revoking, or if we are rejecting the PUT "content-type": "application/json", "headers": undefined, "name": "ResourceReplace", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16298,6 +16323,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17755,6 +17781,7 @@ If some of the permissions specified are already assigned, will skip them.", "content-type": "application/json", "headers": undefined, "name": "RoleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18265,6 +18292,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "RoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19427,6 +19455,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "TenantUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20674,6 +20703,7 @@ The tenant defines the scope of the assignment. In other words, the role is effe "content-type": "application/json", "headers": undefined, "name": "UserRoleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21201,6 +21231,7 @@ If the role is not actually assigned, will return 404.", "content-type": "application/json", "headers": undefined, "name": "UserRoleRemove", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -21318,6 +21349,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "UserUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/readonly.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/readonly.json index 43ecad9fb91..f674df642c1 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/readonly.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/readonly.json @@ -50,6 +50,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/rightbrain.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/rightbrain.json index 27c27f17ddd..be2c6d41b94 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/rightbrain.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/rightbrain.json @@ -16583,6 +16583,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ChatCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16882,6 +16883,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ChatIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16953,6 +16955,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ChatIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17096,6 +17099,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ChatConfig", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17153,6 +17157,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ChatUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17915,6 +17920,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CollectionUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18094,6 +18100,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CollectionCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18320,6 +18327,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CollectionIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18395,6 +18403,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "CollectionIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18578,6 +18587,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "CollectionQuery", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19423,6 +19433,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "BodyGenerateOrgOrgIdProjectProjectIdComposeGeneratePost", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19536,6 +19547,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "BodySectionTalkingPointsOrgOrgIdProjectProjectIdComposeSectionTalkingPointsPost", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19591,6 +19603,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ComposeTopicPoint", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19662,6 +19675,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "BodyUpdateSectionOrgOrgIdProjectProjectIdComposeUpdateSectionPost", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25648,6 +25662,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_document_create_org__org_id__project__project_id__document_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26048,6 +26063,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ChatDocumentUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26121,6 +26137,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_document_upload_org__org_id__project__project_id__document_upload_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26293,6 +26310,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DocumentIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26368,6 +26386,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "DocumentIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27234,6 +27253,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OAuthClientCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27284,6 +27304,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "HydraWebhookTokenHook", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27931,6 +27952,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrgCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28067,6 +28089,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrganizationDomainCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28169,6 +28192,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrgInviteBase", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28387,6 +28411,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrgUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28437,6 +28462,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_org_update_avatar_org__org_id__avatar_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28597,6 +28623,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrganizationIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28668,6 +28695,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "OrganizationIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29358,6 +29386,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ProjectCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29636,6 +29665,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ProjectIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29709,6 +29739,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ProjectIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29817,6 +29848,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "multipart/form-data", "headers": undefined, "name": "Body_project_update_avatar_org__org_id__project__project_id__avatar_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29913,6 +29945,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ProjectConfig", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29968,6 +30001,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ProjectUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30850,6 +30884,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SiteScrapeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31189,6 +31224,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskForwarderCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31438,6 +31474,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskForwarderUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31878,6 +31915,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32023,6 +32061,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32495,6 +32534,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_Task_Run_Parameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32667,6 +32707,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32742,6 +32783,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "TaskIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34015,6 +34057,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserProfileUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34058,6 +34101,7 @@ types: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_user_update_avatar_user_avatar_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/seam.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/seam.json index d15940babc1..6390c88dce5 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/seam.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/seam.json @@ -10033,6 +10033,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10139,6 +10140,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesCreateMultipleRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10210,6 +10212,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10287,6 +10290,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesGenerateCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10373,6 +10377,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10453,6 +10458,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10531,6 +10537,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesPullBackupAccessCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10631,6 +10638,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11499,6 +11507,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateCreateUnmanagedAccessCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11655,6 +11664,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedConvertToManagedRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11716,6 +11726,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11792,6 +11803,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11864,6 +11876,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11920,6 +11933,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12282,6 +12296,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsAddUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12344,6 +12359,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12416,6 +12432,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12492,6 +12509,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsListUsersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12553,6 +12571,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsRemoveUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12918,6 +12937,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialPoolsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13087,6 +13107,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialProvisioningAutomationsLaunchRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13285,6 +13306,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsAssignRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13406,6 +13428,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13457,6 +13480,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13550,6 +13574,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13627,6 +13652,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13702,6 +13728,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsListAccessibleEntrancesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13806,6 +13833,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsUnassignRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13901,6 +13929,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14692,6 +14721,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14753,6 +14783,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesGrantAccessRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14834,6 +14865,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14922,6 +14954,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesListCredentialsWithAccessRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15278,6 +15311,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SystemsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15358,6 +15392,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SystemsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15440,6 +15475,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SystemsListCompatibleCredentialManagerAcsSystemsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15735,6 +15771,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersAddToAccessGroupRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15844,6 +15881,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15895,6 +15933,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15968,6 +16007,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16053,6 +16093,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16128,6 +16169,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersListAccessibleEntrancesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16189,6 +16231,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersRemoveFromAccessGroupRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16239,6 +16282,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersRevokeAccessToAllEntrancesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16289,6 +16333,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersSuspendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16339,6 +16384,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersUnsuspendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16424,6 +16470,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17153,6 +17200,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ActionAttemptsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17207,6 +17255,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ActionAttemptsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17406,6 +17455,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17457,6 +17507,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17515,6 +17566,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17585,6 +17637,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsGetOrCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17647,6 +17700,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsGrantAccessRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17710,6 +17764,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17761,6 +17816,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsRevokeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18246,6 +18302,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18297,6 +18354,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18374,6 +18432,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18452,6 +18511,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18978,6 +19038,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectedAccountsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19098,6 +19159,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectedAccountsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19174,6 +19236,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectedAccountsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19601,6 +19664,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19903,6 +19967,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20024,6 +20089,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20075,6 +20141,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesListDeviceProvidersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20133,6 +20200,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21055,6 +21123,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateConnectRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21105,6 +21174,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateDisconnectRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21155,6 +21225,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateRemoveRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21410,6 +21481,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21523,6 +21595,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21576,6 +21649,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22198,6 +22272,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EventsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22291,6 +22366,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EventsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23752,6 +23828,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23921,6 +23998,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23983,6 +24061,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksLockDoorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24045,6 +24124,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksUnlockDoorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25110,6 +25190,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NetworksGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25157,6 +25238,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NetworksListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25359,6 +25441,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25431,6 +25514,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25490,6 +25574,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25553,6 +25638,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25630,6 +25716,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26008,6 +26095,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateTriggerNoiseThresholdRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26121,6 +26209,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PhonesDeactivateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26206,6 +26295,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PhonesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26447,6 +26537,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateCreateSandboxPhoneRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26734,6 +26825,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsCoolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27037,6 +27129,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27101,6 +27194,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsHeatRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27167,6 +27261,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsHeatCoolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27288,6 +27383,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27350,6 +27446,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsOffRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27414,6 +27511,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsSetFanModeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27467,6 +27565,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28613,6 +28712,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28664,6 +28764,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28741,6 +28842,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28815,6 +28917,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28898,6 +29001,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29345,6 +29449,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesAddAcsUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29422,6 +29527,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29473,6 +29579,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29579,6 +29686,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesGrantAccessToDeviceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29639,6 +29747,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29786,6 +29895,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListAccessibleDevicesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29868,6 +29978,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListAcsSystemsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29944,6 +30055,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListAcsUsersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30005,6 +30117,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesRemoveAcsUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30065,6 +30178,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesRevokeAccessToDeviceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30143,6 +30257,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30991,6 +31106,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31048,6 +31164,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31136,6 +31253,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsLaunchRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31196,6 +31314,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31574,6 +31693,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31617,6 +31737,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31667,6 +31788,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31753,6 +31875,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32067,6 +32190,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WorkspacesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/squidex.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/squidex.json index 7cfc2fd9025..33b57d2f7cc 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/squidex.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/squidex.json @@ -17262,6 +17262,7 @@ You will be assigned as owner of the new app automatically.", "content-type": "application/json", "headers": undefined, "name": "CreateAppDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17347,6 +17348,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "CreateClientDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17500,6 +17502,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "AddLanguageDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17587,6 +17590,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "AddRoleDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17676,6 +17680,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "AddWorkflowDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17756,6 +17761,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateAppDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17832,6 +17838,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "TransferToTeamDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17923,6 +17930,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateAssetScriptsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18026,6 +18034,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateClientDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18115,6 +18124,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateLanguageDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18207,6 +18217,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateRoleDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18307,6 +18318,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateAppSettingsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18416,6 +18428,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateWorkflowDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18489,6 +18502,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "multipart/form-data", "headers": undefined, "name": "AppsUploadImageRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20216,6 +20230,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "BulkUpdateAssetsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20972,6 +20987,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "AssetsPostAssetRequest", + "path-parameters": undefined, "query-parameters": { "duplicate": { "docs": "True to duplicate the asset, event if the file has been uploaded.", @@ -21060,6 +21076,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateAssetFolderDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21152,6 +21169,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "AssetsPostUpsertAssetRequest", + "path-parameters": undefined, "query-parameters": { "duplicate": { "docs": "True to duplicate the asset, event if the file has been uploaded.", @@ -21271,6 +21289,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AnnotateAssetDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21363,6 +21382,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "AssetsPutAssetContentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21439,6 +21459,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "RenameAssetFolderDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21507,6 +21528,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "MoveAssetFolderDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21601,6 +21623,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "MoveAssetDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21667,6 +21690,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "RenameTagDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23049,6 +23073,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "RestoreRequestDto", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -23792,6 +23817,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "BulkUpdateContentsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25319,6 +25345,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ImportContentsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25755,6 +25782,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ChangeStatusDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29605,6 +29633,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UpdateRuleDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32797,6 +32826,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateSchemaDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -33092,6 +33122,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ChangeCategoryDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -33869,6 +33900,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConfigureFieldRulesDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34062,6 +34094,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UpdateSchemaDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34377,6 +34410,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SynchronizeSchemaDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34536,6 +34570,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConfigureUiFieldsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -39604,6 +39639,7 @@ You will be assigned as owner of the new team automatically.", "content-type": "application/json", "headers": undefined, "name": "CreateTeamDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -39677,6 +39713,7 @@ You will be assigned as owner of the new team automatically.", "content-type": "application/json", "headers": undefined, "name": "UpdateTeamDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40275,6 +40312,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TranslateDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40639,6 +40677,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateUserDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40735,6 +40774,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UpdateUserDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/streaming.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/streaming.json index 3c3360ffa81..e5f60aaff72 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/streaming.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/streaming.json @@ -40,6 +40,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -83,6 +84,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -127,6 +129,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV2Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -172,6 +175,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV2StreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -215,6 +219,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV3Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -260,6 +265,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV3StreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/suger.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/suger.json index 3c5d65fd960..5d0bcf4c166 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/suger.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/suger.json @@ -7621,6 +7621,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SharedGetApiClientAccessTokenParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7903,6 +7904,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "GithubComSugerioMarketplaceServiceRdsDbLibUpdateBuyerNameDescriptionParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8157,6 +8159,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedAddEntitlementCreditParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8820,6 +8823,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "GithubComSugerioMarketplaceServiceRdsDbLibUpdateEntitlementNameParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9901,6 +9905,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedCreateIntegrationParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10258,6 +10263,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedUpdateIntegrationParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11576,6 +11582,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SharedCreateUsageRecordGroupParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14285,6 +14292,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedUpdateProductParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14782,6 +14790,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedGetRevenueReportParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14851,6 +14860,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedGetUsageReportParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/superagent.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/superagent.json index 9ceb37087e9..46f2bad87d8 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/superagent.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/superagent.json @@ -508,6 +508,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "Agent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -695,6 +696,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "PredictAgent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -920,6 +922,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "ApiToken", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1163,6 +1166,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "SignIn", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1212,6 +1216,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "SignUp", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1354,6 +1359,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "Document", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1615,6 +1621,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "Prompt", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1935,6 +1942,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "Tool", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/switchboard.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/switchboard.json index 00af67e1ef3..0695f1d3262 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/switchboard.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/switchboard.json @@ -1414,6 +1414,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ExtensionsPostRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1848,6 +1849,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ProfileInput", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2346,6 +2348,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SessionRestInputV1", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2919,6 +2922,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PromptContentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3090,6 +3094,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SummarizeContentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uint.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uint.json index c5cdae9be46..9b212e39608 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uint.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uint.json @@ -36,6 +36,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uploadcare.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uploadcare.json index 0a87b4343f8..bbb088c9c6a 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uploadcare.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/uploadcare.json @@ -3486,6 +3486,7 @@ processing operations. "content-type": "multipart/form-data", "headers": undefined, "name": "CreateFilesGroupRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4241,6 +4242,7 @@ Log.d("TAG", file.toString()) "content-type": "multipart/form-data", "headers": undefined, "name": "BaseUploadRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4886,6 +4888,7 @@ to the value of the `check_URL_duplicates` parameter. "content-type": "multipart/form-data", "headers": undefined, "name": "FromUrlUploadRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5744,6 +5747,7 @@ Log.d("TAG", file.toString()) "content-type": "multipart/form-data", "headers": undefined, "name": "MultipartFileUploadCompleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6082,6 +6086,7 @@ to us as a value of the `part_size` parameter (in bytes). "docs": "Multipart upload parameters to start multipart uploading.", "headers": undefined, "name": "MultipartFileUploadStartRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/valtown.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/valtown.json index 65912287dc6..cb5e76975ef 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/valtown.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/valtown.json @@ -1792,6 +1792,7 @@ docs: Blobs "content-type": "application/json", "headers": undefined, "name": "EmailsSendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3289,6 +3290,7 @@ docs: Search "content-type": "application/json", "headers": undefined, "name": "SqliteBatchRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3371,6 +3373,7 @@ docs: Search "content-type": "application/json", "headers": undefined, "name": "SqliteExecuteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4033,6 +4036,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4141,6 +4145,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsCreateVersionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4301,6 +4306,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "EvalPostRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4567,6 +4573,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsPutRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -4640,6 +4647,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "RunPostRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -4715,6 +4723,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/vellum.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/vellum.json index da6ff8ba893..4165199b999 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/vellum.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/vellum.json @@ -373,6 +373,7 @@ Executes a deployed Workflow and streams back its results.", "content-type": "application/json", "headers": undefined, "name": "ExecuteWorkflowStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -470,6 +471,7 @@ Generate a completion using a previously defined deployment. "content-type": "application/json", "headers": undefined, "name": "GenerateBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -566,6 +568,7 @@ Generate a stream of completions using a previously defined deployment. "content-type": "application/json", "headers": undefined, "name": "GenerateStreamBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -659,6 +662,7 @@ Perform a search against a document index. "content-type": "application/json", "headers": undefined, "name": "SearchRequestBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -726,6 +730,7 @@ Used to submit feedback regarding the quality of previously generated completion "content-type": "application/json", "headers": undefined, "name": "SubmitCompletionActualsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -4743,6 +4748,7 @@ Creates a new document index.", "content-type": "application/json", "headers": undefined, "name": "DocumentIndexCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5057,6 +5063,7 @@ Upload a document to be indexed and used for search. "content-type": "multipart/form-data", "headers": undefined, "name": "UploadDocumentBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5256,6 +5263,7 @@ Compiles the prompt backing the model version using the provided input values.", "content-type": "application/json", "headers": undefined, "name": "ModelVersionCompilePromptRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5612,6 +5620,7 @@ Under the hood, this endpoint creates a new sandbox, a new model version, and a "content-type": "application/json", "headers": undefined, "name": "RegisterPromptRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5934,6 +5943,7 @@ or overwritten with default values.", "content-type": "application/json", "headers": undefined, "name": "UpsertSandboxScenarioRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6208,6 +6218,7 @@ or overwritten with default values.", "content-type": "application/json", "headers": undefined, "name": "TestSuiteTestCaseRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/webflow.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/webflow.json index c9cd0616387..c777f33b6e3 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/webflow.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/webflow.json @@ -9067,6 +9067,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AssetsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9137,6 +9138,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AssetsCreateFolderRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9478,6 +9480,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AssetsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10865,6 +10868,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CollectionsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11769,6 +11773,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "FieldsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11847,6 +11852,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "FieldsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12324,6 +12330,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsCreateItemRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12404,6 +12411,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsCreateItemForMultipleLocalesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12488,6 +12496,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsCreateItemLiveRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12978,6 +12987,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsPublishItemRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13087,6 +13097,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsUpdateItemRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13196,6 +13207,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsUpdateItemLiveRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15766,6 +15778,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "FormsUpdateSubmissionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17081,6 +17094,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "InventoryUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18985,6 +18999,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "OrdersRefundRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19286,6 +19301,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "OrdersUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19576,6 +19592,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "OrdersUpdateFulfillRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35878,6 +35895,7 @@ webhooks: "content-type": "application/json", "headers": undefined, "name": "UpdatePageSettingsRequest", + "path-parameters": undefined, "query-parameters": { "locale": { "docs": "Unique identifier for a specific locale. Applicable, when using localization.", @@ -35960,6 +35978,7 @@ Required scope | `pages:write` "content-type": "application/json", "headers": undefined, "name": "UpdateStaticContentRequest", + "path-parameters": undefined, "query-parameters": { "locale": { "docs": "The locale identifier.", @@ -37838,6 +37857,7 @@ webhooks: "content-type": "application/json", "headers": undefined, "name": "ScriptsUpsertCustomCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38431,6 +38451,7 @@ Required scope | `ecommerce:write`", "content-type": "application/json", "headers": undefined, "name": "ProductsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38527,6 +38548,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "ProductsCreateSkuRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38947,6 +38969,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "ProductsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -39054,6 +39077,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "ProductsUpdateSkuRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -45456,6 +45480,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ScriptsRegisterHostedRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -45540,6 +45565,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ScriptsRegisterInlineRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -46389,6 +46415,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SitesPublishRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -48053,6 +48080,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ScriptsUpsertCustomCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -49288,6 +49316,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersInviteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -49528,6 +49557,7 @@ Can be prefixed with a `-` to reverse the sort (ex. `-CreatedOn`) "content-type": "application/json", "headers": undefined, "name": "UsersUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -52023,6 +52053,7 @@ webhooks: "content-type": "application/json", "headers": undefined, "name": "WebhooksCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-audiences.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-audiences.json index e45c3ddb45f..9ea17ce95a6 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-audiences.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-audiences.json @@ -36,6 +36,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostV1UsersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-global-headers.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-global-headers.json index df5e1639863..3918bfd4d6d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-global-headers.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-global-headers.json @@ -116,6 +116,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -169,6 +170,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-idempotency-headers.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-idempotency-headers.json index 0980331ce22..e2c9691ca60 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-idempotency-headers.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-idempotency-headers.json @@ -39,6 +39,7 @@ "content-type": "application/json", "headers": undefined, "name": "SendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-sdk-group-name-with-streaming.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-sdk-group-name-with-streaming.json index ec27fb6b7f3..ab43256e6ff 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-sdk-group-name-with-streaming.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-sdk-group-name-with-streaming.json @@ -109,6 +109,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -155,6 +156,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-audiences.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-audiences.json index 8f48f3e4607..f4893974af2 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-audiences.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-audiences.json @@ -115,6 +115,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -167,6 +168,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-reference.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-reference.json index ee65c3623e7..947e7655e1e 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-reference.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-reference.json @@ -78,6 +78,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatCompletionsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -119,6 +120,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatCompletionsCreateStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-sse.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-sse.json index 244017d7964..798002653c1 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-sse.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-sse.json @@ -62,6 +62,7 @@ "content-type": "application/json", "headers": undefined, "name": "CreateCompletionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-stream-condition.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-stream-condition.json index 02d4b166cc0..c2486c68f87 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-stream-condition.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-streaming-with-stream-condition.json @@ -47,6 +47,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -95,6 +96,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-token-variable-name.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-token-variable-name.json index 3f059d479f5..cb4279a8c61 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-token-variable-name.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-token-variable-name.json @@ -72,6 +72,7 @@ "content-type": "application/json", "headers": undefined, "name": "CreateCompletionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-version.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-version.json index e21a557ae7e..3ae0294234c 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-version.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/x-fern-version.json @@ -72,6 +72,7 @@ "content-type": "application/json", "headers": undefined, "name": "CreateCompletionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/inline-path-parameters.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/inline-path-parameters.json new file mode 100644 index 00000000000..62dca3ae02c --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/inline-path-parameters.json @@ -0,0 +1,438 @@ +{ + "title": "Inlined path parameters", + "servers": [], + "tags": { + "tagsById": {} + }, + "hasEndpointsMarkedInternal": false, + "endpoints": [ + { + "audiences": [], + "operationId": "GetUser", + "tags": [], + "pathParameters": [ + { + "name": "user_id", + "schema": { + "schema": { + "type": "string" + }, + "generatedName": "GetUserRequestUserId", + "groupName": [], + "type": "primitive" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "queryParameters": [], + "headers": [], + "generatedRequestName": "GetUserRequest", + "errors": {}, + "server": [], + "description": "Returns the user with the provided user ID.", + "authed": false, + "method": "GET", + "path": "/users/{user_id}", + "examples": [ + { + "pathParameters": [ + { + "name": "user_id", + "value": { + "value": { + "value": "user_id", + "type": "string" + }, + "type": "primitive" + } + } + ], + "queryParameters": [], + "headers": [], + "codeSamples": [], + "type": "full" + } + ], + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "audiences": [], + "operationId": "GetOrganizationUser", + "tags": [], + "pathParameters": [ + { + "name": "organization_id", + "schema": { + "schema": { + "type": "string" + }, + "generatedName": "GetOrganizationUserRequestOrganizationId", + "groupName": [], + "type": "primitive" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "name": "user_id", + "schema": { + "schema": { + "type": "string" + }, + "generatedName": "GetOrganizationUserRequestUserId", + "groupName": [], + "type": "primitive" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "queryParameters": [ + { + "name": "limit", + "schema": { + "schema": { + "type": "int" + }, + "generatedName": "GetOrganizationUserRequestLimit", + "groupName": [], + "type": "primitive" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "headers": [], + "generatedRequestName": "GetOrganizationUserRequest", + "response": { + "description": "Successful response", + "schema": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "getOrganizationUserResponseResults", + "key": "results", + "schema": { + "generatedName": "getOrganizationUserResponseResults", + "value": { + "value": { + "schema": { + "type": "string" + }, + "generatedName": "GetOrganizationUserResponseResultsItem", + "groupName": [], + "type": "primitive" + }, + "generatedName": "GetOrganizationUserResponseResults", + "groupName": [], + "type": "array" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "GetOrganizationUserResponse", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "fullExamples": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "json" + }, + "errors": {}, + "server": [], + "description": "Returns the user with the provided user ID.", + "authed": false, + "method": "GET", + "path": "/organizations/{organization_id}/users/{user_id}", + "examples": [ + { + "pathParameters": [ + { + "name": "organization_id", + "value": { + "value": { + "value": "organization_id", + "type": "string" + }, + "type": "primitive" + } + }, + { + "name": "user_id", + "value": { + "value": { + "value": "user_id", + "type": "string" + }, + "type": "primitive" + } + } + ], + "queryParameters": [ + { + "name": "limit", + "value": { + "value": { + "value": 1, + "type": "int" + }, + "type": "primitive" + } + } + ], + "headers": [], + "response": { + "value": { + "properties": { + "results": { + "value": [ + { + "value": { + "value": "results", + "type": "string" + }, + "type": "primitive" + } + ], + "type": "array" + } + }, + "type": "object" + }, + "type": "withoutStreaming" + }, + "codeSamples": [], + "type": "full" + } + ], + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "summary": "Search an organization", + "audiences": [], + "operationId": "Search", + "tags": [ + "Organizations" + ], + "pathParameters": [ + { + "name": "id", + "schema": { + "description": "Bookmark ID", + "schema": { + "type": "string" + }, + "generatedName": "SearchRequestId", + "groupName": [], + "type": "primitive" + }, + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "queryParameters": [], + "headers": [], + "generatedRequestName": "SearchRequest", + "request": { + "schema": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "searchRequestAccess", + "key": "access", + "schema": { + "generatedName": "searchRequestAccess", + "value": { + "description": "Access level", + "generatedName": "SearchRequestAccess", + "values": [ + { + "generatedName": "private", + "value": "private", + "casing": {} + }, + { + "generatedName": "public", + "value": "public", + "casing": {} + } + ], + "groupName": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "enum" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "SearchRequest", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "contentType": "application/json", + "fullExamples": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "json" + }, + "response": { + "description": "Successful response", + "schema": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "searchResponseResults", + "key": "results", + "schema": { + "generatedName": "searchResponseResults", + "value": { + "value": { + "schema": { + "type": "string" + }, + "generatedName": "SearchResponseResultsItem", + "groupName": [], + "type": "primitive" + }, + "generatedName": "SearchResponseResults", + "groupName": [], + "type": "array" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "SearchResponse", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "fullExamples": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "json" + }, + "errors": {}, + "server": [], + "description": "", + "authed": false, + "method": "POST", + "path": "/organizations/{organization_id}/search", + "examples": [ + { + "pathParameters": [ + { + "name": "id", + "value": { + "value": { + "value": "id", + "type": "string" + }, + "type": "primitive" + } + } + ], + "queryParameters": [], + "headers": [], + "request": { + "properties": {}, + "type": "object" + }, + "response": { + "value": { + "properties": { + "results": { + "value": [ + { + "value": { + "value": "results", + "type": "string" + }, + "type": "primitive" + } + ], + "type": "array" + } + }, + "type": "object" + }, + "type": "withoutStreaming" + }, + "codeSamples": [], + "type": "full" + } + ], + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "webhooks": [], + "channel": [], + "groupedSchemas": { + "rootSchemas": {}, + "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/anyOf.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/anyOf.json index d6d5a2b41e9..b0be7e7f005 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/anyOf.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/anyOf.json @@ -38,6 +38,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/apiture.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/apiture.json index 9097ed97c5f..0c12db90011 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/apiture.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/apiture.json @@ -7470,6 +7470,7 @@ The authenticated user must have the `account.allows.manageJointOwners` permissi "content-type": "application/json", "headers": undefined, "name": "NewJointOwnerInvitation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7740,6 +7741,7 @@ The user must have the `allows.manageOverdraftAccounts` permission on the accoun "content-type": "application/json", "headers": undefined, "name": "OverdraftProtectionPatch", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8596,6 +8598,7 @@ Note: This operation requires an identity challenge if the financial institution }, }, "name": "NewTransfer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8939,6 +8942,7 @@ Note: This operation requires an identity challenge if the financial institution }, }, "name": "TransferPatch", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/aries.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/aries.json index 57ac850af35..a2ba8aa0d7c 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/aries.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/aries.json @@ -11463,6 +11463,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "PerformRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11550,6 +11551,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SendMenu", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11766,6 +11768,7 @@ docs: Menu interaction over connection "content-type": "application/json", "headers": undefined, "name": "SendMessage", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12068,6 +12071,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "CreateInvitationRequest", + "path-parameters": undefined, "query-parameters": { "alias": { "docs": "Alias", @@ -12205,6 +12209,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "ConnectionStaticRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12607,6 +12612,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "ReceiveInvitationRequest", + "path-parameters": undefined, "query-parameters": { "alias": { "docs": "Alias", @@ -12674,6 +12680,7 @@ docs: Simple messaging "content-type": "application/json", "headers": undefined, "name": "ConnectionMetadataSetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13706,6 +13713,7 @@ docs: Connection management "content-type": "application/json", "headers": undefined, "name": "CredentialDefinitionSendRequest", + "path-parameters": undefined, "query-parameters": { "conn_id": { "docs": "Connection identifier", @@ -14509,6 +14517,7 @@ docs: Credential definition operations "content-type": "application/json", "headers": undefined, "name": "W3CCredentialsListRequest", + "path-parameters": undefined, "query-parameters": { "count": { "docs": "Maximum number to retrieve", @@ -15238,6 +15247,7 @@ docs: Holder credential management "content-type": "application/json", "headers": undefined, "name": "DIDXRequest", + "path-parameters": undefined, "query-parameters": { "alias": { "docs": "Alias for connection", @@ -16236,6 +16246,7 @@ docs: Feature discovery v2 "content-type": "application/json", "headers": undefined, "name": "Date", + "path-parameters": undefined, "query-parameters": { "endorser_write_txn": { "docs": "Endorser will write the transaction after endorsing it", @@ -17483,6 +17494,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17697,6 +17709,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialConnFreeOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18385,6 +18398,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialIssueRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18432,6 +18446,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18608,6 +18623,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialBoundOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19039,6 +19055,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialProposalRequestMand", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19264,6 +19281,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialFreeOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19533,6 +19551,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialProposalRequestOpt", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19711,6 +19730,7 @@ docs: Introduction of known parties "content-type": "application/json", "headers": undefined, "name": "V10CredentialStoreRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21848,6 +21868,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20IssueCredSchemaCore", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22112,6 +22133,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredOfferConnFreeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23116,6 +23138,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredIssueRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23163,6 +23186,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredIssueProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23417,6 +23441,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredBoundOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23655,6 +23680,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24171,6 +24197,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredOfferRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24696,6 +24723,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredRequestFree", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24953,6 +24981,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20CredStoreRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27468,6 +27497,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SignRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27517,6 +27547,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "VerifyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27656,6 +27687,7 @@ docs: Sign and verify json-ld data "content-type": "application/json", "headers": undefined, "name": "TAAAccept", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28505,6 +28537,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "AdminMediationDeny", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28876,6 +28909,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "MediationCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28929,6 +28963,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "KeylistQueryFilterRequest", + "path-parameters": undefined, "query-parameters": { "paginate_limit": { "docs": "limit number of results", @@ -29007,6 +29042,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "KeylistUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29125,6 +29161,7 @@ docs: Interaction with ledger "content-type": "application/json", "headers": undefined, "name": "MediationIdMatchInfo", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29804,6 +29841,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateWalletRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29854,6 +29892,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "RemoveWalletRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29901,6 +29940,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateWalletTokenRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30058,6 +30098,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateWalletRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30602,6 +30643,7 @@ docs: Multitenant wallet management "content-type": "application/json", "headers": undefined, "name": "InvitationCreateRequest", + "path-parameters": undefined, "query-parameters": { "auto_accept": { "docs": "Auto-accept connection (defaults to configuration)", @@ -31394,6 +31436,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationCreateRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32031,6 +32074,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32271,6 +32315,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationSendRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32508,6 +32553,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationProposalRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32898,6 +32944,7 @@ docs: Out-of-band connections "content-type": "application/json", "headers": undefined, "name": "V10PresentationSendRequestToProposal", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34613,6 +34660,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresCreateRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35212,6 +35260,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresProblemReportRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35428,6 +35477,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresSendRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35645,6 +35695,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresProposalRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35832,6 +35883,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresSpecByFormatRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -36000,6 +36052,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "V20PresentationSendRequestToProposal", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37576,6 +37629,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "ClearPendingRevocationsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37672,6 +37726,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "RevRegCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38512,6 +38567,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "RevokeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38724,6 +38780,7 @@ docs: did resolver interface. "content-type": "application/json", "headers": undefined, "name": "RevRegUpdateTailsFileUri", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -39920,6 +39977,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SchemaSendRequest", + "path-parameters": undefined, "query-parameters": { "conn_id": { "docs": "Connection identifier", @@ -40520,6 +40578,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PingRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40635,6 +40694,7 @@ docs: Trust-ping over connection "content-type": "application/json", "headers": undefined, "name": "DIDCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40889,6 +40949,7 @@ docs: Trust-ping over connection "content-type": "application/json", "headers": undefined, "name": "DIDEndpointWithType", + "path-parameters": undefined, "query-parameters": { "conn_id": { "docs": "Connection identifier", diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/assembly.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/assembly.json index 85b4122c673..873ec3fce50 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/assembly.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/assembly.json @@ -3278,6 +3278,7 @@ The LLM response data, as well as any context provided in the original request w "content-type": "application/json", "headers": undefined, "name": "LemurQuestionAnswerParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3335,6 +3336,7 @@ The LLM response data, as well as any context provided in the original request w "content-type": "application/json", "headers": undefined, "name": "LemurSummaryParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3392,6 +3394,7 @@ The LLM response data, as well as any context provided in the original request w "content-type": "application/json", "headers": undefined, "name": "LemurTaskParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3677,6 +3680,7 @@ docs: LeMUR related operations "content-type": "application/json", "headers": undefined, "name": "CreateRealtimeTemporaryTokenParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3964,6 +3968,7 @@ docs: Real-time transcription "content-type": "application/json", "headers": undefined, "name": "CreateTranscriptParameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/availability.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/availability.json index 66577281dc5..c64a46f497d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/availability.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/availability.json @@ -124,6 +124,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostSuccessInlineBetaBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -160,6 +161,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostSuccessInlineObjectDeprecationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -199,6 +201,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostSuccessInlinePropertyDeprecationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -238,6 +241,7 @@ "content-type": "application/json", "headers": undefined, "name": "Settings", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/axle.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/axle.json index 5ee10880330..e30fe4b42d1 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/axle.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/axle.json @@ -921,6 +921,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "StartIgnitionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1335,6 +1336,7 @@ Auth codes are ephemeral and expire after 10 minutes, while accessTokens do not "content-type": "application/json", "headers": undefined, "name": "ExchangeTokenRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/belvo.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/belvo.json index 3d29bb0e123..2654a04520c 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/belvo.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/belvo.json @@ -28812,6 +28812,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "BalancesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -31036,6 +31037,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CategorizationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32396,6 +32398,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "EmploymentRecordRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -32880,6 +32883,7 @@ docs: "# Employment Records\n\nOur employment records\_resource lets you get a c "content-type": "application/json", "headers": undefined, "name": "EyodIncomeVerificationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -33955,6 +33959,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "IncomesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -41024,6 +41029,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "InvoicesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -44527,6 +44533,7 @@ For single links, you have to perform POST calls to an institution **every time* "content-type": "application/json", "headers": undefined, "name": "ChangeAccessMode", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -45472,6 +45479,7 @@ For a list of standards codes, see the table below. "content-type": "application/json", "headers": undefined, "name": "LinksRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -45703,6 +45711,7 @@ Belvo supports a base64 encoded `private_key`. If the `private_key` parameter is "content-type": "application/json", "headers": undefined, "name": "LinksPutRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -49202,6 +49211,7 @@ A payment intent captures all payment information (such as the amount to be char "content-type": "application/json", "headers": undefined, "name": "CreatePaymentIntentPse", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -49709,6 +49719,7 @@ During the payment intent flow, you need to fill in the payment intent with requ "content-type": "application/json", "headers": undefined, "name": "PatchPaymentIntentPse", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -51841,6 +51852,7 @@ docs: > "content-type": "application/json", "headers": undefined, "name": "PaymentWebhookRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -52433,6 +52445,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "ReceivableTransactionRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -53825,6 +53838,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "RecurringExpensesRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -57655,6 +57669,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxComplianceStatusRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -59167,6 +59182,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxDeclarationsRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -60468,6 +60484,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxRetentionsRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", @@ -70854,6 +70871,7 @@ When set to `false`, the data won't be persisted and we return a 200 OK response "content-type": "application/json", "headers": undefined, "name": "TaxStatusRequest", + "path-parameters": undefined, "query-parameters": { "fields": { "docs": "Return only the specified fields in the response. For more information, see our [Filtering responses](https://developers.belvo.com/docs/searching-and-filtering) DevPortal article.", diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/content-type.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/content-type.json index aab8b728460..bcd94e25c9a 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/content-type.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/content-type.json @@ -56,6 +56,7 @@ "content-type": "multipart/form-data", "headers": undefined, "name": "PostTestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/deel.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/deel.json index 39563a6b7cc..15bfe45a120 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/deel.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/deel.json @@ -11205,6 +11205,7 @@ docs: End-points to retrieve paid invoices and reciepts. "content-type": "application/json", "headers": undefined, "name": "InputToCreateFileRef", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11667,6 +11668,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PremiumToAddContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11728,6 +11730,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToAmendDetailsContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11898,6 +11901,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12065,6 +12069,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerOngoingTimeBased", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12230,6 +12235,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerPaygMilestones", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12395,6 +12401,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerPaygTasks", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12562,6 +12569,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToCreateContainerPayAsYouGoTimeBased", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12725,6 +12733,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ContractToTerminateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13654,6 +13663,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "docs": "This is the file you will upload in a multi-part form.", "headers": undefined, "name": "AddContractDocumentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13762,6 +13772,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "InputToPatchContractExternalId", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13895,6 +13906,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "docs": "This is the file you will upload in a multi-part form.", "headers": undefined, "name": "EditContractDocumentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14307,6 +14319,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "ContractInvitationToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14376,6 +14389,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "EstimateFirstPaymentContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14439,6 +14453,7 @@ docs: The Contracts resource lets you create, amend and, retrieve Deel contracts "content-type": "application/json", "headers": undefined, "name": "ContractSignatureToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15264,6 +15279,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "EorContractToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15797,6 +15813,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15860,6 +15877,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentReviewToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15917,6 +15935,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentReviewsToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16321,6 +16340,7 @@ docs: API end-points related to HRIS data. "content-type": "application/json", "headers": undefined, "name": "InvoiceAdjustmentToUpdateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17294,6 +17314,7 @@ docs: Helper end-points for choosing options for other operations "content-type": "application/json", "headers": undefined, "name": "MilestoneToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17362,6 +17383,7 @@ docs: Helper end-points for choosing options for other operations "content-type": "application/json", "headers": undefined, "name": "MilestoneReviewToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17428,6 +17450,7 @@ docs: Helper end-points for choosing options for other operations "content-type": "application/json", "headers": undefined, "name": "MilestoneReviewsToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17962,6 +17985,7 @@ docs: Helper end-points for managing milestones of Deel contracts "content-type": "application/json", "headers": undefined, "name": "OffCyclePaymentToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18696,6 +18720,7 @@ docs: Helper end-points for additional information about your organizations "content-type": "application/json", "headers": undefined, "name": "InputToCreatePgoTask", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18773,6 +18798,7 @@ docs: Helper end-points for additional information about your organizations "content-type": "application/json", "headers": undefined, "name": "RequestBodyToCreatePgoTaskReviewsReviewsContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18851,6 +18877,7 @@ docs: Helper end-points for additional information about your organizations "content-type": "application/json", "headers": undefined, "name": "RequestBodyToCreatePgoTaskReviewsByIdReviewsContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19868,6 +19895,7 @@ docs: End-points to manage tasks for a given contract "content-type": "application/json", "headers": undefined, "name": "TimeoffToReviewContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20432,6 +20460,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20495,6 +20524,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetReviewToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20552,6 +20582,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetReviewsToCreateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21007,6 +21038,7 @@ docs: Manage time off requests. "content-type": "application/json", "headers": undefined, "name": "TimesheetToUpdateContainer", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21635,6 +21667,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "PatchWebhookRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21787,6 +21820,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateWebhookRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/devrev.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/devrev.json index 0b2db96ebf5..eb425cf5e35 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/devrev.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/devrev.json @@ -5063,6 +5063,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ArtifactsPrepareRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5261,6 +5262,7 @@ connections can not be deleted using this method. "content-type": "application/json", "headers": undefined, "name": "DevOrgAuthConnectionsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -5406,6 +5408,7 @@ disabled. "content-type": "application/json", "headers": undefined, "name": "DevOrgAuthConnectionsToggleRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -5784,6 +5787,7 @@ requesting an application access token (AAT). "content-type": "application/json", "headers": undefined, "name": "AuthTokensCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5831,6 +5835,7 @@ JTI claim of the token in the authorization header. "content-type": "application/json", "headers": undefined, "name": "AuthTokensDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -6039,6 +6044,7 @@ authenticated user. "content-type": "application/json", "headers": undefined, "name": "AuthTokensSelfDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -6131,6 +6137,7 @@ organization. "content-type": "application/json", "headers": undefined, "name": "AuthTokensUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6983,6 +6990,7 @@ docs: Dev user interactions. "content-type": "application/json", "headers": undefined, "name": "PartsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7735,6 +7743,7 @@ records. "content-type": "application/json", "headers": undefined, "name": "RevOrgsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7784,6 +7793,7 @@ records. "content-type": "application/json", "headers": undefined, "name": "RevOrgsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8086,6 +8096,7 @@ records. "content-type": "application/json", "headers": undefined, "name": "RevOrgsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8559,6 +8570,7 @@ permitted. "content-type": "application/json", "headers": undefined, "name": "TagsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8608,6 +8620,7 @@ permitted. "content-type": "application/json", "headers": undefined, "name": "TagsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8856,6 +8869,7 @@ tags. "content-type": "application/json", "headers": undefined, "name": "TagsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9883,6 +9897,7 @@ response. "content-type": "application/json", "headers": undefined, "name": "WebhooksCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9932,6 +9947,7 @@ response. "content-type": "application/json", "headers": undefined, "name": "WebhooksDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10175,6 +10191,7 @@ won't receive any object events until successfully verified. "content-type": "application/json", "headers": undefined, "name": "WebhooksUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10679,6 +10696,7 @@ docs: Webhook event APIs. "content-type": "application/json", "headers": undefined, "name": "WorksDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/discriminated-union-value-title.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/discriminated-union-value-title.json index 3444dc5d076..5303ec20f77 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/discriminated-union-value-title.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/discriminated-union-value-title.json @@ -38,6 +38,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/file-upload.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/file-upload.json index 5129ba9cb5f..e5e600efba6 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/file-upload.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/file-upload.json @@ -40,6 +40,7 @@ "content-type": "multipart/form-data", "headers": undefined, "name": "UploadFileRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flagright.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flagright.json index 4a3911dda60..51b778e1009 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flagright.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flagright.json @@ -1195,6 +1195,7 @@ In order to make individual events retrievable, you also need to pass in a uniqu "content-type": "application/json", "headers": undefined, "name": "BusinessUserEvent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1728,6 +1729,7 @@ In order to make individual events retrievable, you also need to pass in a uniqu "content-type": "application/json", "headers": undefined, "name": "TransactionEvent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1969,6 +1971,7 @@ In order to make individual events retrievable, you also need to pass in a uniqu "content-type": "application/json", "headers": undefined, "name": "ConsumerUserEvent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flexport.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flexport.json index 5ed49213db8..bd92294e044 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flexport.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/flexport.json @@ -8887,6 +8887,7 @@ errors: "content-type": "application/json", "headers": undefined, "name": "CreateBooking", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10575,6 +10576,7 @@ docs: Endpoints relating to Booking objects "content-type": "application/json", "headers": undefined, "name": "CreateBookingAmendment", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10946,6 +10948,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateBookingLineItem", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11532,6 +11535,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateCarbonCalculation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11985,6 +11989,7 @@ docs: Endpoints relating to Carbon Calculations "content-type": "application/json", "headers": undefined, "name": "CreateCommercialInvoice", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12483,6 +12488,7 @@ docs: Endpoints relating to Carbon Calculations "content-type": "application/json", "headers": undefined, "name": "UpdateCommercialInvoice", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13252,6 +13258,7 @@ docs: Endpoints relating to Commercial Invoice objects "content-type": "application/json", "headers": undefined, "name": "CreateCompany", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13655,6 +13662,7 @@ docs: Endpoints relating to Commercial Invoice objects "content-type": "application/json", "headers": undefined, "name": "UpdateCompany", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14377,6 +14385,7 @@ docs: Endpoints relating to Company objects "content-type": "application/json", "headers": undefined, "name": "CreateCompanyEntity", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14531,6 +14540,7 @@ docs: Endpoints relating to Company objects "content-type": "application/json", "headers": undefined, "name": "UpdateCompanyEntity", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15008,6 +15018,7 @@ docs: Endpoints relating to CompanyEntity objects "content-type": "application/json", "headers": undefined, "name": "CreateContact", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15244,6 +15255,7 @@ docs: Endpoints relating to CompanyEntity objects "content-type": "application/json", "headers": undefined, "name": "UpdateContact", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19333,6 +19345,7 @@ docs: Endpoints relating to Invoice objects "content-type": "application/json", "headers": undefined, "name": "CreateLocation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19509,6 +19522,7 @@ docs: Endpoints relating to Invoice objects "content-type": "application/json", "headers": undefined, "name": "UpdateLocation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20392,6 +20406,7 @@ docs: Endpoints relating to Ports objects "content-type": "application/json", "headers": undefined, "name": "CreateProduct", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20673,6 +20688,7 @@ docs: Endpoints relating to Ports objects "content-type": "application/json", "headers": undefined, "name": "UpdateProduct", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24332,6 +24348,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "ShipmentsShareableRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25016,6 +25033,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpdateShipment", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/float.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/float.json index 7d5966c4d3b..3f9e90b448d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/float.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/float.json @@ -35,6 +35,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hathora.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hathora.json index 9eb00b966a7..752e05f7853 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hathora.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hathora.json @@ -1372,6 +1372,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LoginGoogleRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1418,6 +1419,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LoginNicknameRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1767,6 +1769,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "RunBuildRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2678,6 +2681,7 @@ service: }, }, "name": "CreateLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -2755,6 +2759,7 @@ service: }, }, "name": "CreateLocalLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -2832,6 +2837,7 @@ service: }, }, "name": "CreatePrivateLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -2909,6 +2915,7 @@ service: }, }, "name": "CreatePublicLobbyRequest", + "path-parameters": undefined, "query-parameters": { "roomId": "optional", }, @@ -3069,6 +3076,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SetLobbyStateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4091,6 +4099,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateRoomRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hookdeck.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hookdeck.json index 77e5ea539bd..b22acd31772 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hookdeck.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hookdeck.json @@ -4455,6 +4455,7 @@ docs: An attempt is any request that Hookdeck makes on behalf of an event. "content-type": "application/json", "headers": undefined, "name": "CreateBookmarkRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4726,6 +4727,7 @@ docs: An attempt is any request that Hookdeck makes on behalf of an event. "content-type": "application/json", "headers": undefined, "name": "TriggerBookmarkRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4828,6 +4830,7 @@ docs: An attempt is any request that Hookdeck makes on behalf of an event. "content-type": "application/json", "headers": undefined, "name": "UpdateBookmarkRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5316,6 +5319,7 @@ docs: A bookmark lets you conveniently store and replay a specific request. "content-type": "application/json", "headers": undefined, "name": "CreateEventBulkRetryRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6738,6 +6742,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "CreateIgnoredEventBulkRetryRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7354,6 +7359,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "CreateRequestBulkRetryRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8666,6 +8672,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "CreateConnectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9324,6 +9331,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "UpdateConnectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9485,6 +9493,7 @@ docs: '' "content-type": "application/json", "headers": undefined, "name": "UpsertConnectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10749,6 +10758,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "CreateDestinationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11002,6 +11012,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpdateDestinationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11080,6 +11091,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpsertDestinationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12362,6 +12374,7 @@ docs: An event is any request that Hookdeck receives from a source. "content-type": "application/json", "headers": undefined, "name": "CreateIntegrationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12619,6 +12632,7 @@ docs: An event is any request that Hookdeck receives from a source. "content-type": "application/json", "headers": undefined, "name": "UpdateIntegrationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13045,6 +13059,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateIssueTriggerRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13369,6 +13384,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateIssueTriggerRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13443,6 +13459,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpsertIssueTriggerRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14312,6 +14329,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "UpdateIssueRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14831,6 +14849,7 @@ docs: >- "content-type": "application/json", "headers": undefined, "name": "ToggleWebhookNotificationsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15444,6 +15463,7 @@ docs: Notifications let your team receive alerts anytime an issue changes. "content-type": "application/json", "headers": undefined, "name": "RetryRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16051,6 +16071,7 @@ docs: A request represent a webhook received by Hookdeck. "content-type": "application/json", "headers": undefined, "name": "CreateRulesetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16293,6 +16314,7 @@ docs: A request represent a webhook received by Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpdateRulesetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16361,6 +16383,7 @@ docs: A request represent a webhook received by Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpsertRulesetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16806,6 +16829,7 @@ docs: A ruleset defines a group of rules that can be used across many connection "content-type": "application/json", "headers": undefined, "name": "CreateSourceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17056,6 +17080,7 @@ docs: A ruleset defines a group of rules that can be used across many connection "content-type": "application/json", "headers": undefined, "name": "UpdateSourceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17122,6 +17147,7 @@ docs: A ruleset defines a group of rules that can be used across many connection "content-type": "application/json", "headers": undefined, "name": "UpsertSourceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17540,6 +17566,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "CreateTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17891,6 +17918,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "TestTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17965,6 +17993,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpdateTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18035,6 +18064,7 @@ docs: A source represents any third party that sends webhooks to Hookdeck. "content-type": "application/json", "headers": undefined, "name": "UpsertTransformationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/humanloop.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/humanloop.json index d291e99a403..eb6c0e1f9ee 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/humanloop.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/humanloop.json @@ -6230,6 +6230,7 @@ If `"add"` or `"remove"`, one of the `version_id` or `environment` query paramet "content-type": "application/json", "headers": undefined, "name": "DatasetRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag to identify a deployed Version to base the created Version on. Only used when `action` is `"add"` or `"remove"`.", @@ -6814,6 +6815,7 @@ By default the deployed version of the Dataset is returned. Use the query parame "content-type": "application/json", "headers": undefined, "name": "UpdateDatasetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8254,6 +8256,7 @@ as completed.", "content-type": "application/json", "headers": undefined, "name": "BodyEvaluationsUpdateStatus", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9054,6 +9057,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "EvaluatorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9316,6 +9320,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "RunSyncEvaluationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9852,6 +9857,7 @@ By default the deployed version of the Evaluator is returned. Use the query para "content-type": "application/json", "headers": undefined, "name": "UpdateEvaluatorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11584,6 +11590,7 @@ in the case where you are storing or deriving your Prompt details in code.", "content-type": "application/json", "headers": undefined, "name": "PromptCallRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag of the deployed version to log to.", @@ -11897,6 +11904,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "PromptRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12574,6 +12582,7 @@ in the case where you are storing or deriving your Prompt details in code.", "content-type": "application/json", "headers": undefined, "name": "PromptLogRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag of the deployed version to log to.", @@ -12703,6 +12712,7 @@ in the case where you are storing or deriving your Prompt details in code.", "content-type": "application/json", "headers": undefined, "name": "UpdatePromptRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14885,6 +14895,7 @@ an exception will be raised.", "content-type": "application/json", "headers": undefined, "name": "ToolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15475,6 +15486,7 @@ in the case where you are storing or deriving your Tool details in code.", "content-type": "application/json", "headers": undefined, "name": "ToolLogRequest", + "path-parameters": undefined, "query-parameters": { "environment": { "docs": "An environment tag of the deployed version to log to.", @@ -15580,6 +15592,7 @@ in the case where you are storing or deriving your Tool details in code.", "content-type": "application/json", "headers": undefined, "name": "UpdateToolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hume.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hume.json index 27e80d62e42..74ab8cf13fa 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hume.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/hume.json @@ -179,6 +179,7 @@ If you wish to supply more than 100 URLs, consider providing them as an archive "content-type": "application/json; charset=utf-8", "headers": undefined, "name": "BaseRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/inline-path-parameters.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/inline-path-parameters.json new file mode 100644 index 00000000000..a47eed7d1b8 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/inline-path-parameters.json @@ -0,0 +1,297 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "GetOrganizationUser": { + "auth": false, + "docs": "Returns the user with the provided user ID.", + "examples": [ + { + "path-parameters": { + "organization_id": "organization_id", + "user_id": "user_id", + }, + "query-parameters": { + "limit": 1, + }, + "response": { + "body": { + "results": [ + "results", + ], + }, + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/organizations/{organization_id}/users/{user_id}", + "request": { + "name": "GetOrganizationUserRequest", + "path-parameters": { + "organization_id": "string", + "user_id": "string", + }, + "query-parameters": { + "limit": "integer", + }, + }, + "response": { + "docs": "Successful response", + "type": "GetOrganizationUserResponse", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "GetUser": { + "auth": false, + "docs": "Returns the user with the provided user ID.", + "examples": [ + { + "path-parameters": { + "user_id": "user_id", + }, + }, + ], + "method": "GET", + "pagination": undefined, + "path": "/users/{user_id}", + "request": { + "name": "GetUserRequest", + "path-parameters": { + "user_id": "string", + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "GetOrganizationUserResponse": { + "docs": undefined, + "properties": { + "results": "optional>", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "service: + auth: false + base-path: '' + endpoints: + GetUser: + path: /users/{user_id} + method: GET + auth: false + docs: Returns the user with the provided user ID. + source: + openapi: ../openapi.yml + request: + name: GetUserRequest + path-parameters: + user_id: string + examples: + - path-parameters: + user_id: user_id + GetOrganizationUser: + path: /organizations/{organization_id}/users/{user_id} + method: GET + auth: false + docs: Returns the user with the provided user ID. + source: + openapi: ../openapi.yml + request: + name: GetOrganizationUserRequest + path-parameters: + organization_id: string + user_id: string + query-parameters: + limit: integer + response: + docs: Successful response + type: GetOrganizationUserResponse + examples: + - path-parameters: + organization_id: organization_id + user_id: user_id + query-parameters: + limit: 1 + response: + body: + results: + - results + source: + openapi: ../openapi.yml +types: + GetOrganizationUserResponse: + properties: + results: optional> + source: + openapi: ../openapi.yml +", + }, + "organizations.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "Search": { + "auth": false, + "display-name": "Search an organization", + "docs": "", + "examples": [ + { + "path-parameters": { + "id": "id", + }, + "request": {}, + "response": { + "body": { + "results": [ + "results", + ], + }, + }, + }, + ], + "method": "POST", + "pagination": undefined, + "path": "/organizations/{organization_id}/search", + "request": { + "body": { + "properties": { + "access": { + "docs": "Access level", + "type": "optional", + }, + }, + }, + "content-type": "application/json", + "headers": undefined, + "name": "SearchRequest", + "path-parameters": { + "id": "string", + }, + "query-parameters": undefined, + }, + "response": { + "docs": "Successful response", + "type": "SearchResponse", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "SearchRequestAccess": { + "docs": "Access level", + "enum": [ + "private", + "public", + ], + "source": { + "openapi": "../openapi.yml", + }, + }, + "SearchResponse": { + "docs": undefined, + "properties": { + "results": "optional>", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "types: + SearchRequestAccess: + enum: + - private + - public + docs: Access level + source: + openapi: ../openapi.yml + SearchResponse: + properties: + results: optional> + source: + openapi: ../openapi.yml +service: + auth: false + base-path: '' + endpoints: + Search: + path: /organizations/{organization_id}/search + method: POST + auth: false + docs: '' + source: + openapi: ../openapi.yml + display-name: Search an organization + request: + name: SearchRequest + path-parameters: + id: string + body: + properties: + access: + type: optional + docs: Access level + content-type: application/json + response: + docs: Successful response + type: SearchResponse + examples: + - path-parameters: + id: id + request: {} + response: + body: + results: + - results + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "Inlined path parameters", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: Inlined path parameters +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/intercom.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/intercom.json index e585a0a09f2..55a9ceb8bfc 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/intercom.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/intercom.json @@ -11788,6 +11788,7 @@ You can view the currently authorised admin along with the embedded app object ( "content-type": "application/json", "headers": undefined, "name": "SetAwayAdminRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18153,6 +18154,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AttachContactToACompanyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20239,6 +20241,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "MergeContactsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20810,6 +20813,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22989,6 +22993,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "AttachContactToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23503,6 +23508,7 @@ It is not possible to use this endpoint with Workflows. "content-type": "application/json", "headers": undefined, "name": "ConvertConversationToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23592,6 +23598,7 @@ This will return the Message model that has been created. "content-type": "application/json", "headers": undefined, "name": "CreateConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24337,6 +24344,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "DetachContactFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27662,6 +27670,7 @@ If you want to reply to a coveration or take an action such as assign, unassign, "content-type": "application/json", "headers": undefined, "name": "UpdateConversationRequest", + "path-parameters": undefined, "query-parameters": { "display_as": { "docs": "Set to plaintext to retrieve conversation messages in plain text.", @@ -32559,6 +32568,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -33185,6 +33195,7 @@ You can update a data attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -34300,6 +34311,7 @@ Duplicated events are responded to using the normal `202 Accepted` code - an err "content-type": "application/json", "headers": undefined, "name": "CreateDataEventSummariesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -34898,6 +34910,7 @@ The only parameters you need to provide are the range of dates that you want exp "content-type": "application/json", "headers": undefined, "name": "CreateDataExportsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35708,6 +35721,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -36618,6 +36632,7 @@ Collections will be returned in descending order on the `updated_at` attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -39728,6 +39743,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateNoteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40723,6 +40739,7 @@ This will return a subscription type model for the subscription type that was ad "content-type": "application/json", "headers": undefined, "name": "AttachSubscriptionTypeToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41474,6 +41491,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41563,6 +41581,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41652,6 +41671,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41963,6 +41983,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -42074,6 +42095,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43168,6 +43190,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43285,6 +43308,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -44237,6 +44261,7 @@ docs: Everything about your ticket types "content-type": "application/json", "headers": undefined, "name": "CreateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -45573,6 +45598,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -47642,6 +47668,7 @@ docs: Everything about your tickets "content-type": "application/json", "headers": undefined, "name": "ConvertVisitorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/merge.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/merge.json index ac78aea9b6e..95f50fc2068 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/merge.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/merge.json @@ -19452,6 +19452,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LinkedAccountSelectiveSyncConfigurationListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/oauth.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/oauth.json index 0003523d14a..55f9269234b 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/oauth.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/oauth.json @@ -99,6 +99,7 @@ "content-type": "application/json", "headers": undefined, "name": "AuthGetTokenRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/only-include-referenced-schemas.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/only-include-referenced-schemas.json index 3d927651404..5128a4ed477 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/only-include-referenced-schemas.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/only-include-referenced-schemas.json @@ -11281,6 +11281,7 @@ You can view the currently authorised admin along with the embedded app object ( "content-type": "application/json", "headers": undefined, "name": "SetAwayAdminRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17646,6 +17647,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AttachContactToACompanyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19732,6 +19734,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "MergeContactsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20303,6 +20306,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22482,6 +22486,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "AttachContactToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22996,6 +23001,7 @@ It is not possible to use this endpoint with Workflows. "content-type": "application/json", "headers": undefined, "name": "ConvertConversationToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23085,6 +23091,7 @@ This will return the Message model that has been created. "content-type": "application/json", "headers": undefined, "name": "CreateConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23830,6 +23837,7 @@ If you add a contact via the email parameter and there is no user/lead found on "content-type": "application/json", "headers": undefined, "name": "DetachContactFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27155,6 +27163,7 @@ If you want to reply to a coveration or take an action such as assign, unassign, "content-type": "application/json", "headers": undefined, "name": "UpdateConversationRequest", + "path-parameters": undefined, "query-parameters": { "display_as": { "docs": "Set to plaintext to retrieve conversation messages in plain text.", @@ -32052,6 +32061,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32678,6 +32688,7 @@ You can update a data attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateDataAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -33793,6 +33804,7 @@ Duplicated events are responded to using the normal `202 Accepted` code - an err "content-type": "application/json", "headers": undefined, "name": "CreateDataEventSummariesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -34316,6 +34328,7 @@ The only parameters you need to provide are the range of dates that you want exp "content-type": "application/json", "headers": undefined, "name": "CreateDataExportsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -35126,6 +35139,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -36036,6 +36050,7 @@ Collections will be returned in descending order on the `updated_at` attribute. "content-type": "application/json", "headers": undefined, "name": "UpdateCollectionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -39146,6 +39161,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateNoteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40141,6 +40157,7 @@ This will return a subscription type model for the subscription type that was ad "content-type": "application/json", "headers": undefined, "name": "AttachSubscriptionTypeToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40892,6 +40909,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToContactRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40981,6 +40999,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41070,6 +41089,7 @@ docs: Everything about Switch "content-type": "application/json", "headers": undefined, "name": "AttachTagToTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41381,6 +41401,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromConversationRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41492,6 +41513,7 @@ Each operation will return a tag object. "content-type": "application/json", "headers": undefined, "name": "DetachTagFromTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -42586,6 +42608,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CreateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -42703,6 +42726,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UpdateTicketTypeAttributeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -43655,6 +43679,7 @@ docs: Everything about your ticket types "content-type": "application/json", "headers": undefined, "name": "CreateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -44991,6 +45016,7 @@ The table below shows the operators you can use to define how you want to search "content-type": "application/json", "headers": undefined, "name": "UpdateTicketRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -47060,6 +47086,7 @@ docs: Everything about your tickets "content-type": "application/json", "headers": undefined, "name": "ConvertVisitorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/permit.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/permit.json index e4494ad68f3..b29b1af9e1c 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/permit.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/permit.json @@ -4423,6 +4423,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "TenantCreateBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4493,6 +4494,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserCreateBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4550,6 +4552,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "TenantDeleteBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4607,6 +4610,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserDeleteBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4677,6 +4681,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserReplaceBulkOperation", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5027,6 +5032,7 @@ If the permission is already granted, it is skipped.", "content-type": "application/json", "headers": undefined, "name": "ConditionSetRuleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5196,6 +5202,7 @@ If the permission is not granted, it is skipped.", "content-type": "application/json", "headers": undefined, "name": "ConditionSetRuleRemove", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -5669,6 +5676,7 @@ If we check the checkbox where `us_based_employees` and `private_repos->clone` a "content-type": "application/json", "headers": undefined, "name": "ConditionSetCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6426,6 +6434,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ConditionSetUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7319,6 +7328,7 @@ allowed.", "content-type": "application/json", "headers": undefined, "name": "EnvironmentCopy", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7775,6 +7785,7 @@ allowed.", "content-type": "application/json", "headers": undefined, "name": "EnvironmentUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8352,6 +8363,7 @@ authenticated actor (i.e: human team member or api key).", "content-type": "application/json", "headers": undefined, "name": "OrganizationCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8594,6 +8606,7 @@ authenticated actor (i.e: human team member or api key).", "content-type": "application/json", "headers": undefined, "name": "OrganizationUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8957,6 +8970,7 @@ Every project is a separate silo, and has its own unique set of environments and "content-type": "application/json", "headers": undefined, "name": "ProjectCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9178,6 +9192,7 @@ Every project is a separate silo, and has its own unique set of environments and "content-type": "application/json", "headers": undefined, "name": "ProjectUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9543,6 +9558,7 @@ to a role as one action. "content-type": "application/json", "headers": undefined, "name": "ResourceActionGroupCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9838,6 +9854,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceActionGroupUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10306,6 +10323,7 @@ Each (resource, action) pair defines a unique permission level. "content-type": "application/json", "headers": undefined, "name": "ResourceActionCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10591,6 +10609,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceActionUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11758,6 +11777,7 @@ and will return the existing instance object in the response body.", "content-type": "application/json", "headers": undefined, "name": "ResourceInstanceCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12020,6 +12040,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceInstanceUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12557,6 +12578,7 @@ If some of the permissions specified are already assigned, will skip them.", "content-type": "application/json", "headers": undefined, "name": "ResourceRoleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13096,6 +13118,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13931,6 +13954,7 @@ A resource may also contain: "content-type": "application/json", "headers": undefined, "name": "ResourceCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14342,6 +14366,7 @@ TODO: we need to decide if we are auto-revoking, or if we are rejecting the PUT "content-type": "application/json", "headers": undefined, "name": "ResourceReplace", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14480,6 +14505,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "ResourceUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15937,6 +15963,7 @@ If some of the permissions specified are already assigned, will skip them.", "content-type": "application/json", "headers": undefined, "name": "RoleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16447,6 +16474,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "RoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17609,6 +17637,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "TenantUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18856,6 +18885,7 @@ The tenant defines the scope of the assignment. In other words, the role is effe "content-type": "application/json", "headers": undefined, "name": "UserRoleCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19383,6 +19413,7 @@ If the role is not actually assigned, will return 404.", "content-type": "application/json", "headers": undefined, "name": "UserRoleRemove", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -19500,6 +19531,7 @@ Fields that will be provided will be completely overwritten.", "content-type": "application/json", "headers": undefined, "name": "UserUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/readonly.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/readonly.json index 43ecad9fb91..f674df642c1 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/readonly.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/readonly.json @@ -50,6 +50,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/rightbrain.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/rightbrain.json index 2ab2c2c70ed..5ea4a3e78fc 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/rightbrain.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/rightbrain.json @@ -4139,6 +4139,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ChatCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4438,6 +4439,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ChatIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4509,6 +4511,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ChatIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4652,6 +4655,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ChatConfig", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4709,6 +4713,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ChatUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5471,6 +5476,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CollectionUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5650,6 +5656,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CollectionCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5876,6 +5883,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "CollectionIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5951,6 +5959,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "CollectionIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6134,6 +6143,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "CollectionQuery", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -6949,6 +6959,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "BodyGenerateOrgOrgIdProjectProjectIdComposeGeneratePost", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7062,6 +7073,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "BodySectionTalkingPointsOrgOrgIdProjectProjectIdComposeSectionTalkingPointsPost", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7117,6 +7129,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ComposeTopicPoint", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7188,6 +7201,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "BodyUpdateSectionOrgOrgIdProjectProjectIdComposeUpdateSectionPost", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13013,6 +13027,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_document_create_org__org_id__project__project_id__document_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13413,6 +13428,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ChatDocumentUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13486,6 +13502,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_document_upload_org__org_id__project__project_id__document_upload_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13658,6 +13675,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DocumentIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13733,6 +13751,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "DocumentIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14571,6 +14590,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OAuthClientCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14621,6 +14641,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "HydraWebhookTokenHook", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15261,6 +15282,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrgCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15397,6 +15419,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrganizationDomainCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15499,6 +15522,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrgInviteBase", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15717,6 +15741,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrgUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15767,6 +15792,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_org_update_avatar_org__org_id__avatar_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15927,6 +15953,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "OrganizationIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15998,6 +16025,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "OrganizationIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16683,6 +16711,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ProjectCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16961,6 +16990,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ProjectIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17034,6 +17064,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ProjectIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17142,6 +17173,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "multipart/form-data", "headers": undefined, "name": "Body_project_update_avatar_org__org_id__project__project_id__avatar_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17238,6 +17270,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ProjectConfig", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17293,6 +17326,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "ProjectUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18166,6 +18200,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SiteScrapeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18501,6 +18536,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskForwarderCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18750,6 +18786,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskForwarderUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19190,6 +19227,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskCreate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19335,6 +19373,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19807,6 +19846,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_Task_Run_Parameters", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19979,6 +20019,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TaskIAMPermissionTest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20054,6 +20095,7 @@ This may be combined with `remove_all` to replace the member's entire role list. "content-type": "application/json", "headers": undefined, "name": "TaskIAMMemberRoleUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21327,6 +21369,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "UserProfileUpdate", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21370,6 +21413,7 @@ types: "content-type": "multipart/form-data", "headers": undefined, "name": "Body_user_update_avatar_user_avatar_post", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/seam.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/seam.json index a3cbf9f8698..15d60a3d54e 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/seam.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/seam.json @@ -7765,6 +7765,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7871,6 +7872,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesCreateMultipleRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7942,6 +7944,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8019,6 +8022,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesGenerateCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8105,6 +8109,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8185,6 +8190,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8263,6 +8269,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesPullBackupAccessCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8363,6 +8370,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "AccessCodesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9231,6 +9239,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateCreateUnmanagedAccessCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9387,6 +9396,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedConvertToManagedRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9448,6 +9458,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9524,6 +9535,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9596,6 +9608,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9652,6 +9665,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10014,6 +10028,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsAddUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10076,6 +10091,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10148,6 +10164,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10224,6 +10241,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsListUsersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10285,6 +10303,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AccessGroupsRemoveUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10650,6 +10669,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialPoolsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10819,6 +10839,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialProvisioningAutomationsLaunchRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11017,6 +11038,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsAssignRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11138,6 +11160,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11189,6 +11212,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11282,6 +11306,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11359,6 +11384,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11434,6 +11460,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsListAccessibleEntrancesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11538,6 +11565,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsUnassignRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11633,6 +11661,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CredentialsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12424,6 +12453,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12485,6 +12515,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesGrantAccessRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12566,6 +12597,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12654,6 +12686,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EntrancesListCredentialsWithAccessRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13010,6 +13043,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SystemsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13090,6 +13124,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SystemsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13172,6 +13207,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SystemsListCompatibleCredentialManagerAcsSystemsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13465,6 +13501,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersAddToAccessGroupRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13574,6 +13611,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13625,6 +13663,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13698,6 +13737,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13783,6 +13823,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13858,6 +13899,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersListAccessibleEntrancesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13919,6 +13961,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersRemoveFromAccessGroupRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13969,6 +14012,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersRevokeAccessToAllEntrancesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14019,6 +14063,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersSuspendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14069,6 +14114,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersUnsuspendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14154,6 +14200,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14883,6 +14930,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ActionAttemptsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14937,6 +14985,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ActionAttemptsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15136,6 +15185,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15187,6 +15237,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15245,6 +15296,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15315,6 +15367,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsGetOrCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15377,6 +15430,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsGrantAccessRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15440,6 +15494,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15491,6 +15546,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClientSessionsRevokeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -15976,6 +16032,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16027,6 +16084,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16104,6 +16162,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16182,6 +16241,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectWebviewsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16708,6 +16768,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectedAccountsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16828,6 +16889,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectedAccountsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16904,6 +16966,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConnectedAccountsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17331,6 +17394,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17633,6 +17697,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17754,6 +17819,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17805,6 +17871,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesListDeviceProvidersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17863,6 +17930,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "DevicesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18785,6 +18853,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateConnectRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18835,6 +18904,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateDisconnectRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -18885,6 +18955,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateRemoveRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19140,6 +19211,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19253,6 +19325,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19306,6 +19379,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UnmanagedUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -19928,6 +20002,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EventsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -20021,6 +20096,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EventsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21482,6 +21558,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21651,6 +21728,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21713,6 +21791,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksLockDoorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21775,6 +21854,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "LocksUnlockDoorRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22840,6 +22920,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NetworksGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -22887,6 +22968,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NetworksListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23089,6 +23171,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23161,6 +23244,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23220,6 +23304,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23283,6 +23368,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23360,6 +23446,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "NoiseThresholdsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23738,6 +23825,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateTriggerNoiseThresholdRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23851,6 +23939,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PhonesDeactivateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -23936,6 +24025,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PhonesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24177,6 +24267,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SimulateCreateSandboxPhoneRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24464,6 +24555,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsCoolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24767,6 +24859,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24831,6 +24924,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsHeatRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24897,6 +24991,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsHeatCoolRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25018,6 +25113,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25080,6 +25176,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsOffRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25144,6 +25241,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsSetFanModeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25197,6 +25295,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ThermostatsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26343,6 +26442,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26394,6 +26494,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26471,6 +26572,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26545,6 +26647,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26628,6 +26731,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ClimateSettingSchedulesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27075,6 +27179,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesAddAcsUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27152,6 +27257,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27203,6 +27309,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27309,6 +27416,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesGrantAccessToDeviceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27369,6 +27477,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27516,6 +27625,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListAccessibleDevicesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27598,6 +27708,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListAcsSystemsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27674,6 +27785,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesListAcsUsersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27735,6 +27847,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesRemoveAcsUserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27795,6 +27908,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesRevokeAccessToDeviceRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27873,6 +27987,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UserIdentitiesUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28721,6 +28836,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28778,6 +28894,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28866,6 +28983,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsLaunchRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -28926,6 +29044,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "EnrollmentAutomationsListRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29304,6 +29423,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29347,6 +29467,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksDeleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29397,6 +29518,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29483,6 +29605,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WebhooksUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -29797,6 +29920,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "WorkspacesCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/squidex.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/squidex.json index 172b66de183..6530d38ad5b 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/squidex.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/squidex.json @@ -8893,6 +8893,7 @@ You will be assigned as owner of the new app automatically.", "content-type": "application/json", "headers": undefined, "name": "CreateAppDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8978,6 +8979,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "CreateClientDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9131,6 +9133,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "AddLanguageDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9218,6 +9221,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "AddRoleDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9307,6 +9311,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "AddWorkflowDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9387,6 +9392,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateAppDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9463,6 +9469,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "TransferToTeamDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9554,6 +9561,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateAssetScriptsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9657,6 +9665,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateClientDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9746,6 +9755,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateLanguageDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9838,6 +9848,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateRoleDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9938,6 +9949,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateAppSettingsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10047,6 +10059,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "application/json", "headers": undefined, "name": "UpdateWorkflowDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10120,6 +10133,7 @@ The client secret is auto generated on the server and returned. The client does "content-type": "multipart/form-data", "headers": undefined, "name": "AppsUploadImageRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11847,6 +11861,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "BulkUpdateAssetsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12603,6 +12618,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "AssetsPostAssetRequest", + "path-parameters": undefined, "query-parameters": { "duplicate": { "docs": "True to duplicate the asset, event if the file has been uploaded.", @@ -12691,6 +12707,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateAssetFolderDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12783,6 +12800,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "AssetsPostUpsertAssetRequest", + "path-parameters": undefined, "query-parameters": { "duplicate": { "docs": "True to duplicate the asset, event if the file has been uploaded.", @@ -12902,6 +12920,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AnnotateAssetDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -12994,6 +13013,7 @@ service: "content-type": "multipart/form-data", "headers": undefined, "name": "AssetsPutAssetContentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13070,6 +13090,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "RenameAssetFolderDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13138,6 +13159,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "MoveAssetFolderDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13232,6 +13254,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "MoveAssetDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13298,6 +13321,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "RenameTagDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14680,6 +14704,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "RestoreRequestDto", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -15423,6 +15448,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "BulkUpdateContentsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -16950,6 +16976,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ImportContentsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -17386,6 +17413,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ChangeStatusDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -21236,6 +21264,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UpdateRuleDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24428,6 +24457,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateSchemaDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -24723,6 +24753,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ChangeCategoryDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25500,6 +25531,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConfigureFieldRulesDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -25693,6 +25725,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UpdateSchemaDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26008,6 +26041,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SynchronizeSchemaDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -26167,6 +26201,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ConfigureUiFieldsDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31234,6 +31269,7 @@ You will be assigned as owner of the new team automatically.", "content-type": "application/json", "headers": undefined, "name": "CreateTeamDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31307,6 +31343,7 @@ You will be assigned as owner of the new team automatically.", "content-type": "application/json", "headers": undefined, "name": "UpdateTeamDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31905,6 +31942,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "TranslateDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32269,6 +32307,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CreateUserDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -32365,6 +32404,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UpdateUserDto", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/streaming.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/streaming.json index 3c3360ffa81..e5f60aaff72 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/streaming.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/streaming.json @@ -40,6 +40,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -83,6 +84,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -127,6 +129,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV2Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -172,6 +175,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV2StreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -215,6 +219,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV3Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -260,6 +265,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserV3StreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/suger.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/suger.json index b20b430d69c..2a5489a8d60 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/suger.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/suger.json @@ -6928,6 +6928,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SharedGetApiClientAccessTokenParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7210,6 +7211,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "GithubComSugerioMarketplaceServiceRdsDbLibUpdateBuyerNameDescriptionParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7464,6 +7466,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedAddEntitlementCreditParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -8127,6 +8130,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "GithubComSugerioMarketplaceServiceRdsDbLibUpdateEntitlementNameParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9208,6 +9212,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedCreateIntegrationParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9565,6 +9570,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedUpdateIntegrationParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10883,6 +10889,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "SharedCreateUsageRecordGroupParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -13592,6 +13599,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedUpdateProductParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14089,6 +14097,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedGetRevenueReportParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -14158,6 +14167,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SharedGetUsageReportParams", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/superagent.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/superagent.json index c7b05cb2f8a..0ab9c7cac64 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/superagent.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/superagent.json @@ -148,6 +148,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "Agent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -335,6 +336,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "PredictAgent", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -558,6 +560,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "ApiToken", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -799,6 +802,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "SignIn", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -848,6 +852,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "SignUp", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -990,6 +995,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "Document", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1249,6 +1255,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "Prompt", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1567,6 +1574,7 @@ imports: "content-type": "application/json", "headers": undefined, "name": "Tool", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/switchboard.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/switchboard.json index ee5674ac6ef..2fe73005e2d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/switchboard.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/switchboard.json @@ -1306,6 +1306,7 @@ types: "content-type": "application/json", "headers": undefined, "name": "ExtensionsPostRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1740,6 +1741,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ProfileInput", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2238,6 +2240,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SessionRestInputV1", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2811,6 +2814,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "PromptContentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2982,6 +2986,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SummarizeContentRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uint.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uint.json index c5cdae9be46..9b212e39608 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uint.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uint.json @@ -36,6 +36,7 @@ "content-type": "application/json", "headers": undefined, "name": "Request", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uploadcare.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uploadcare.json index 659572e10b1..898bfc05483 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uploadcare.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/uploadcare.json @@ -2297,6 +2297,7 @@ processing operations. "content-type": "multipart/form-data", "headers": undefined, "name": "CreateFilesGroupRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3052,6 +3053,7 @@ Log.d("TAG", file.toString()) "content-type": "multipart/form-data", "headers": undefined, "name": "BaseUploadRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3697,6 +3699,7 @@ to the value of the `check_URL_duplicates` parameter. "content-type": "multipart/form-data", "headers": undefined, "name": "FromUrlUploadRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4555,6 +4558,7 @@ Log.d("TAG", file.toString()) "content-type": "multipart/form-data", "headers": undefined, "name": "MultipartFileUploadCompleteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4893,6 +4897,7 @@ to us as a value of the `part_size` parameter (in bytes). "docs": "Multipart upload parameters to start multipart uploading.", "headers": undefined, "name": "MultipartFileUploadStartRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/valtown.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/valtown.json index 8e9732776dc..f8c4b2f5c66 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/valtown.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/valtown.json @@ -1735,6 +1735,7 @@ docs: Blobs "content-type": "application/json", "headers": undefined, "name": "EmailsSendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3232,6 +3233,7 @@ docs: Search "content-type": "application/json", "headers": undefined, "name": "SqliteBatchRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3314,6 +3316,7 @@ docs: Search "content-type": "application/json", "headers": undefined, "name": "SqliteExecuteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3976,6 +3979,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4084,6 +4088,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsCreateVersionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4244,6 +4249,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "EvalPostRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4510,6 +4516,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsPutRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -4583,6 +4590,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "RunPostRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -4658,6 +4666,7 @@ let you get, create, and run vals.", "content-type": "application/json", "headers": undefined, "name": "ValsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/vellum.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/vellum.json index c63f6004ca5..cb3643c0f57 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/vellum.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/vellum.json @@ -191,6 +191,7 @@ Executes a deployed Workflow and streams back its results.", "content-type": "application/json", "headers": undefined, "name": "ExecuteWorkflowStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -288,6 +289,7 @@ Generate a completion using a previously defined deployment. "content-type": "application/json", "headers": undefined, "name": "GenerateBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -384,6 +386,7 @@ Generate a stream of completions using a previously defined deployment. "content-type": "application/json", "headers": undefined, "name": "GenerateStreamBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { @@ -477,6 +480,7 @@ Perform a search against a document index. "content-type": "application/json", "headers": undefined, "name": "SearchRequestBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -544,6 +548,7 @@ Used to submit feedback regarding the quality of previously generated completion "content-type": "application/json", "headers": undefined, "name": "SubmitCompletionActualsRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { @@ -4470,6 +4475,7 @@ Creates a new document index.", "content-type": "application/json", "headers": undefined, "name": "DocumentIndexCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4784,6 +4790,7 @@ Upload a document to be indexed and used for search. "content-type": "multipart/form-data", "headers": undefined, "name": "UploadDocumentBodyRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4983,6 +4990,7 @@ Compiles the prompt backing the model version using the provided input values.", "content-type": "application/json", "headers": undefined, "name": "ModelVersionCompilePromptRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5339,6 +5347,7 @@ Under the hood, this endpoint creates a new sandbox, a new model version, and a "content-type": "application/json", "headers": undefined, "name": "RegisterPromptRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5661,6 +5670,7 @@ or overwritten with default values.", "content-type": "application/json", "headers": undefined, "name": "UpsertSandboxScenarioRequestRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5935,6 +5945,7 @@ or overwritten with default values.", "content-type": "application/json", "headers": undefined, "name": "TestSuiteTestCaseRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/webflow.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/webflow.json index 00761d5bd79..b31ac9e1559 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/webflow.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/webflow.json @@ -1060,6 +1060,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AssetsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1130,6 +1131,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AssetsCreateFolderRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -1471,6 +1473,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "AssetsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -2858,6 +2861,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "CollectionsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3762,6 +3766,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "FieldsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -3840,6 +3845,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "FieldsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4317,6 +4323,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsCreateItemRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4397,6 +4404,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsCreateItemForMultipleLocalesRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4481,6 +4489,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsCreateItemLiveRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -4971,6 +4980,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsPublishItemRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5080,6 +5090,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsUpdateItemRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -5189,6 +5200,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ItemsUpdateItemLiveRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -7759,6 +7771,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "FormsUpdateSubmissionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -9074,6 +9087,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "InventoryUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -10978,6 +10992,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "OrdersRefundRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11279,6 +11294,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "OrdersUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -11569,6 +11585,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "OrdersUpdateFulfillRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -27871,6 +27888,7 @@ webhooks: "content-type": "application/json", "headers": undefined, "name": "UpdatePageSettingsRequest", + "path-parameters": undefined, "query-parameters": { "locale": { "docs": "Unique identifier for a specific locale. Applicable, when using localization.", @@ -27953,6 +27971,7 @@ Required scope | `pages:write` "content-type": "application/json", "headers": undefined, "name": "UpdateStaticContentRequest", + "path-parameters": undefined, "query-parameters": { "locale": { "docs": "The locale identifier.", @@ -29831,6 +29850,7 @@ webhooks: "content-type": "application/json", "headers": undefined, "name": "ScriptsUpsertCustomCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30424,6 +30444,7 @@ Required scope | `ecommerce:write`", "content-type": "application/json", "headers": undefined, "name": "ProductsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30520,6 +30541,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "ProductsCreateSkuRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -30940,6 +30962,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "ProductsUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -31047,6 +31070,7 @@ Required scope | `ecommerce:write` "content-type": "application/json", "headers": undefined, "name": "ProductsUpdateSkuRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37449,6 +37473,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ScriptsRegisterHostedRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -37533,6 +37558,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ScriptsRegisterInlineRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -38382,6 +38408,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "SitesPublishRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -40046,6 +40073,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "ScriptsUpsertCustomCodeRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41281,6 +41309,7 @@ service: "content-type": "application/json", "headers": undefined, "name": "UsersInviteRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -41521,6 +41550,7 @@ Can be prefixed with a `-` to reverse the sort (ex. `-CreatedOn`) "content-type": "application/json", "headers": undefined, "name": "UsersUpdateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -44016,6 +44046,7 @@ webhooks: "content-type": "application/json", "headers": undefined, "name": "WebhooksCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-audiences.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-audiences.json index e45c3ddb45f..9ea17ce95a6 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-audiences.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-audiences.json @@ -36,6 +36,7 @@ "content-type": "application/json", "headers": undefined, "name": "PostV1UsersRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "source": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-global-headers.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-global-headers.json index df5e1639863..3918bfd4d6d 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-global-headers.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-global-headers.json @@ -116,6 +116,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -169,6 +170,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-idempotency-headers.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-idempotency-headers.json index 0980331ce22..e2c9691ca60 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-idempotency-headers.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-idempotency-headers.json @@ -39,6 +39,7 @@ "content-type": "application/json", "headers": undefined, "name": "SendRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-sdk-group-name-with-streaming.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-sdk-group-name-with-streaming.json index ec27fb6b7f3..ab43256e6ff 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-sdk-group-name-with-streaming.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-sdk-group-name-with-streaming.json @@ -109,6 +109,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -155,6 +156,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-audiences.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-audiences.json index 8f48f3e4607..f4893974af2 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-audiences.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-audiences.json @@ -115,6 +115,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -167,6 +168,7 @@ "content-type": "application/json", "headers": undefined, "name": "UserGetStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-reference.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-reference.json index ee65c3623e7..947e7655e1e 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-reference.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-reference.json @@ -78,6 +78,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatCompletionsCreateRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -119,6 +120,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatCompletionsCreateStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-sse.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-sse.json index 244017d7964..798002653c1 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-sse.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-sse.json @@ -62,6 +62,7 @@ "content-type": "application/json", "headers": undefined, "name": "CreateCompletionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-stream-condition.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-stream-condition.json index 02d4b166cc0..c2486c68f87 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-stream-condition.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-streaming-with-stream-condition.json @@ -47,6 +47,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { @@ -95,6 +96,7 @@ "content-type": "application/json", "headers": undefined, "name": "ChatStreamRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response-stream": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-token-variable-name.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-token-variable-name.json index 3f059d479f5..cb4279a8c61 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-token-variable-name.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-token-variable-name.json @@ -72,6 +72,7 @@ "content-type": "application/json", "headers": undefined, "name": "CreateCompletionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-version.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-version.json index e21a557ae7e..3ae0294234c 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-version.json +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/x-fern-version.json @@ -72,6 +72,7 @@ "content-type": "application/json", "headers": undefined, "name": "CreateCompletionRequest", + "path-parameters": undefined, "query-parameters": undefined, }, "response": { diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/inline-path-parameters/fern/fern.config.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/inline-path-parameters/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/inline-path-parameters/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/inline-path-parameters/fern/generators.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/inline-path-parameters/fern/generators.yml new file mode 100644 index 00000000000..4b20e33eb1d --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/inline-path-parameters/fern/generators.yml @@ -0,0 +1,5 @@ +api: + specs: + - openapi: ../openapi.yml + settings: + inline-path-parameters: true diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/inline-path-parameters/openapi.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/inline-path-parameters/openapi.yml new file mode 100644 index 00000000000..bc3ee33d5e8 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/inline-path-parameters/openapi.yml @@ -0,0 +1,87 @@ +openapi: 3.0.0 +info: + title: Inlined path parameters + version: 1.0.0 +paths: + /users/{user_id}: + get: + operationId: GetUser + description: Returns the user with the provided user ID. + parameters: + - in: path + name: user_id + schema: + type: string + required: true + /organizations/{organization_id}/users/{user_id}: + get: + operationId: GetOrganizationUser + description: Returns the user with the provided user ID. + parameters: + - in: path + name: organization_id + schema: + type: string + required: true + - in: path + name: user_id + schema: + type: string + required: true + - in: query + name: limit + schema: + type: integer + required: true + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: string + /organizations/{organization_id}/search: + post: + operationId: Search + summary: Search an organization + description: "" + tags: + - Organizations + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: string + parameters: + - in: path + name: id + schema: + type: string + description: Bookmark ID + required: true + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + access: + type: string + enum: + - private + - public + description: Access level + additionalProperties: false diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/OpenApiIrConverterContext.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/OpenApiIrConverterContext.ts index 2eee404a724..a3a6e82dc17 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/OpenApiIrConverterContext.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/OpenApiIrConverterContext.ts @@ -50,6 +50,11 @@ export interface OpenApiIrConverterContextOpts { * If true, the converter will only include schemas referenced by endpoints. */ onlyIncludeReferencedSchemas: boolean; + + /** + * If true, the converter will include path parameters in the in-lined request. + */ + inlinePathParameters: boolean; } export class OpenApiIrConverterContext { @@ -64,6 +69,7 @@ export class OpenApiIrConverterContext { public objectQueryParameters: boolean; public respectReadonlySchemas: boolean; public onlyIncludeReferencedSchemas: boolean; + public inlinePathParameters: boolean; private enableUniqueErrorsPerEndpoint: boolean; private defaultServerName: string | undefined = undefined; @@ -100,7 +106,8 @@ export class OpenApiIrConverterContext { authOverrides, objectQueryParameters, respectReadonlySchemas, - onlyIncludeReferencedSchemas + onlyIncludeReferencedSchemas, + inlinePathParameters }: OpenApiIrConverterContextOpts) { this.logger = taskContext.logger; this.taskContext = taskContext; @@ -117,6 +124,7 @@ export class OpenApiIrConverterContext { this.objectQueryParameters = objectQueryParameters; this.respectReadonlySchemas = respectReadonlySchemas; this.onlyIncludeReferencedSchemas = onlyIncludeReferencedSchemas; + this.inlinePathParameters = inlinePathParameters; this.referencedSchemaIds = onlyIncludeReferencedSchemas ? new Set() : undefined; const schemaByStatusCode: Record = {}; diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts index b5851cad795..0652f43e711 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern/src/buildEndpoint.ts @@ -100,7 +100,10 @@ export function buildEndpoint({ source: endpoint.source != null ? convertToSourceSchema(endpoint.source) : undefined }; - if (Object.keys(pathParameters).length > 0) { + if ( + !endpointRequestSupportsInlinedPathParameters({ context, request: endpoint.request }) && + Object.keys(pathParameters).length > 0 + ) { convertedEndpoint["path-parameters"] = pathParameters; } @@ -133,6 +136,8 @@ export function buildEndpoint({ request: endpoint.request, generatedRequestName: endpoint.generatedRequestName, requestNameOverride: endpoint.requestNameOverride ?? undefined, + pathParameters: + context.inlinePathParameters && Object.keys(pathParameters).length > 0 ? pathParameters : undefined, queryParameters: Object.keys(queryParameters).length > 0 ? queryParameters : undefined, nonRequestReferencedSchemas: Array.from(nonRequestReferencedSchemas), headers: Object.keys(headers).length > 0 ? headers : undefined, @@ -143,14 +148,18 @@ export function buildEndpoint({ schemaIdsToExclude = [...schemaIdsToExclude, ...(convertedRequest.schemaIdsToExclude ?? [])]; context.unsetInState(State.Request); } else { + const hasPathParams = context.inlinePathParameters && Object.keys(pathParameters).length > 0; const hasQueryParams = Object.keys(queryParameters).length > 0; const hasHeaders = Object.keys(headers).length > 0; const convertedRequest: RawSchemas.HttpRequestSchema = {}; - if (hasQueryParams || hasHeaders) { + if (hasPathParams || hasQueryParams || hasHeaders) { convertedRequest.name = endpoint.requestNameOverride ?? endpoint.generatedRequestName; } + if (hasPathParams) { + convertedRequest["path-parameters"] = pathParameters; + } if (hasQueryParams) { convertedRequest["query-parameters"] = queryParameters; } @@ -369,6 +378,7 @@ function getRequest({ request, requestNameOverride, generatedRequestName, + pathParameters, queryParameters, nonRequestReferencedSchemas, headers, @@ -381,6 +391,7 @@ function getRequest({ request: Request; requestNameOverride?: string; generatedRequestName: string; + pathParameters?: Record; queryParameters?: Record; nonRequestReferencedSchemas: SchemaId[]; headers?: Record; @@ -410,16 +421,20 @@ function getRequest({ } }; + const hasPathParams = Object.keys(pathParameters ?? {}).length > 0; const hasQueryParams = Object.keys(queryParameters ?? {}).length > 0; const hasHeaders = Object.keys(headers ?? {}).length > 0; + if (hasPathParams) { + convertedRequest.value["path-parameters"] = pathParameters; + } if (hasQueryParams) { convertedRequest.value["query-parameters"] = queryParameters; } if (hasHeaders) { convertedRequest.value.headers = headers; } - if (hasQueryParams || hasHeaders) { + if (hasPathParams || hasQueryParams || hasHeaders) { convertedRequest.value.name = requestNameOverride ?? generatedRequestName; } @@ -524,6 +539,7 @@ function getRequest({ const convertedRequestValue: RawSchemas.HttpRequestSchema = { name: requestNameOverride ?? resolvedSchema.nameOverride ?? resolvedSchema.generatedName, + "path-parameters": pathParameters, "query-parameters": queryParameters, headers, body: requestBodySchema @@ -593,6 +609,7 @@ function getRequest({ schemaIdsToExclude: request.name == null ? [] : [request.name], value: { name: requestNameOverride ?? request.name ?? generatedRequestName, + "path-parameters": pathParameters, "query-parameters": queryParameters, headers, body: { @@ -606,3 +623,30 @@ function getRequest({ assertNever(request); } } + +function endpointRequestSupportsInlinedPathParameters({ + context, + request +}: { + context: OpenApiIrConverterContext; + request: Request | undefined; +}): boolean { + if (!context.inlinePathParameters) { + return false; + } + if (request == null) { + return true; + } + switch (request.type) { + case "octetStream": + // octet-stream requests do not support named request wrappers, + // so we can't inline path parameters for them. + return false; + case "multipart": + return true; + case "json": + return true; + default: + assertNever(request); + } +} diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index b483a63bbdc..44f84efe810 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,3 +1,11 @@ +- changelogEntry: + - summary: | + Add support for the `inline-path-parameters` setting in the OpenAPI + importer. + type: feat + irVersion: 53 + version: 0.45.0-rc51 + - changelogEntry: - summary: | Increase max recursive depth allowed for example validation. diff --git a/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts b/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts index e13d2db0a6d..34d3426cdd2 100644 --- a/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts +++ b/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts @@ -55,6 +55,7 @@ export interface APIDefinitionSettings { objectQueryParameters: boolean | undefined; respectReadonlySchemas: boolean | undefined; onlyIncludeReferencedSchemas: boolean | undefined; + inlinePathParameters: boolean | undefined; } export interface APIDefinitionLocation { diff --git a/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts b/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts index fc3e59bbc13..a45f9e456a8 100644 --- a/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts +++ b/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts @@ -99,7 +99,8 @@ async function parseAPIConfigurationToApiLocations( shouldUseOptionalAdditionalProperties: undefined, coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, - respectReadonlySchemas: undefined + respectReadonlySchemas: undefined, + inlinePathParameters: undefined } }); } else if (isRawProtobufAPIDefinitionSchema(apiConfiguration)) { @@ -121,7 +122,8 @@ async function parseAPIConfigurationToApiLocations( coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, respectReadonlySchemas: undefined, - onlyIncludeReferencedSchemas: undefined + onlyIncludeReferencedSchemas: undefined, + inlinePathParameters: undefined } }); } else if (Array.isArray(apiConfiguration)) { @@ -143,7 +145,8 @@ async function parseAPIConfigurationToApiLocations( coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, respectReadonlySchemas: undefined, - onlyIncludeReferencedSchemas: undefined + onlyIncludeReferencedSchemas: undefined, + inlinePathParameters: undefined } }); } else if (isRawProtobufAPIDefinitionSchema(definition)) { @@ -165,7 +168,8 @@ async function parseAPIConfigurationToApiLocations( coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, respectReadonlySchemas: undefined, - onlyIncludeReferencedSchemas: undefined + onlyIncludeReferencedSchemas: undefined, + inlinePathParameters: undefined } }); } else { @@ -185,7 +189,8 @@ async function parseAPIConfigurationToApiLocations( shouldUseOptionalAdditionalProperties: undefined, coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, - respectReadonlySchemas: undefined + respectReadonlySchemas: undefined, + inlinePathParameters: undefined } }); } @@ -207,7 +212,8 @@ async function parseAPIConfigurationToApiLocations( shouldUseOptionalAdditionalProperties: undefined, coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, - respectReadonlySchemas: undefined + respectReadonlySchemas: undefined, + inlinePathParameters: undefined } }); } @@ -234,7 +240,8 @@ async function parseAPIConfigurationToApiLocations( shouldUseOptionalAdditionalProperties: undefined, coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, - respectReadonlySchemas: undefined + respectReadonlySchemas: undefined, + inlinePathParameters: undefined } }); } else if (openapi != null) { @@ -254,7 +261,8 @@ async function parseAPIConfigurationToApiLocations( shouldUseOptionalAdditionalProperties: undefined, coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, - respectReadonlySchemas: undefined + respectReadonlySchemas: undefined, + inlinePathParameters: undefined } }); } @@ -276,7 +284,8 @@ async function parseAPIConfigurationToApiLocations( shouldUseOptionalAdditionalProperties: undefined, coerceEnumsToLiterals: undefined, objectQueryParameters: undefined, - respectReadonlySchemas: undefined + respectReadonlySchemas: undefined, + inlinePathParameters: undefined } }); } @@ -343,7 +352,8 @@ async function parseApiConfigurationV2Schema({ shouldUseOptionalAdditionalProperties: spec.settings?.["optional-additional-properties"] ?? true, coerceEnumsToLiterals: spec.settings?.["coerce-enums-to-literals"], objectQueryParameters: spec.settings?.["object-query-parameters"], - respectReadonlySchemas: spec.settings?.["respect-readonly-schemas"] + respectReadonlySchemas: spec.settings?.["respect-readonly-schemas"], + inlinePathParameters: spec.settings?.["inline-path-parameters"] } }; if (spec.namespace == null) { diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts index 506852fc95c..58fa44d9d45 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts @@ -10,7 +10,10 @@ export interface OAuthSchemeSchema extends FernDefinition.fernDefinition.WithDoc scopes?: string[]; "client-id-env"?: string; "client-secret-env"?: string; + /** Sets the token header value prefix. Defaults to 'Bearer' */ "token-prefix"?: string; + /** Sets the token header key name. Defaults to 'Authorization' */ + "token-header"?: string; "get-token": FernDefinition.fernDefinition.OAuthGetTokenEndpointSchema; "refresh-token"?: FernDefinition.fernDefinition.OAuthRefreshTokenEndpointSchema; } diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/ApiDefinitionSettingsSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/ApiDefinitionSettingsSchema.ts index 2e7a9e127e1..158d2731a11 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/ApiDefinitionSettingsSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/ApiDefinitionSettingsSchema.ts @@ -13,4 +13,6 @@ export interface ApiDefinitionSettingsSchema { "message-naming"?: FernDefinition.MessageNamingSettingsSchema; /** Whether to only include schemas referenced by endpoints in the generated SDK (i.e. a form of tree-shaking). Defaults to false. */ "only-include-referenced-schemas"?: boolean; + /** Whether to include path parameters within the generated in-lined request. Defaults to false. */ + "inline-path-parameters"?: boolean; } diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts index 3d95f1573fb..f9ac573e392 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/generators/types/OpenApiSettingsSchema.ts @@ -12,4 +12,6 @@ export interface OpenApiSettingsSchema { "respect-readonly-schemas"?: boolean; /** Whether to only include schemas referenced by endpoints in the generated SDK (i.e. a form of tree-shaking). Defaults to false. */ "only-include-referenced-schemas"?: boolean; + /** Whether to include path parameters within the generated in-lined request. Defaults to false. */ + "inline-path-parameters"?: boolean; } diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts index 75247de0df0..336452b6d09 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/fernDefinition/resources/auth/types/OAuthSchemeSchema.ts @@ -20,6 +20,7 @@ export const OAuthSchemeSchema: core.serialization.ObjectSchema< "client-id-env": core.serialization.string().optional(), "client-secret-env": core.serialization.string().optional(), "token-prefix": core.serialization.string().optional(), + "token-header": core.serialization.string().optional(), "get-token": OAuthGetTokenEndpointSchema, "refresh-token": OAuthRefreshTokenEndpointSchema.optional(), }) @@ -33,6 +34,7 @@ export declare namespace OAuthSchemeSchema { "client-id-env"?: string | null; "client-secret-env"?: string | null; "token-prefix"?: string | null; + "token-header"?: string | null; "get-token": OAuthGetTokenEndpointSchema.Raw; "refresh-token"?: OAuthRefreshTokenEndpointSchema.Raw | null; } diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/ApiDefinitionSettingsSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/ApiDefinitionSettingsSchema.ts index 455aa590a07..408b94408ea 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/ApiDefinitionSettingsSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/ApiDefinitionSettingsSchema.ts @@ -16,6 +16,7 @@ export const ApiDefinitionSettingsSchema: core.serialization.ObjectSchema< unions: UnionSettingsSchema.optional(), "message-naming": MessageNamingSettingsSchema.optional(), "only-include-referenced-schemas": core.serialization.boolean().optional(), + "inline-path-parameters": core.serialization.boolean().optional(), }); export declare namespace ApiDefinitionSettingsSchema { @@ -24,5 +25,6 @@ export declare namespace ApiDefinitionSettingsSchema { unions?: UnionSettingsSchema.Raw | null; "message-naming"?: MessageNamingSettingsSchema.Raw | null; "only-include-referenced-schemas"?: boolean | null; + "inline-path-parameters"?: boolean | null; } } diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts index 6d8e002039f..0ebec694b07 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/generators/types/OpenApiSettingsSchema.ts @@ -16,6 +16,7 @@ export const OpenApiSettingsSchema: core.serialization.ObjectSchema< "object-query-parameters": core.serialization.boolean().optional(), "respect-readonly-schemas": core.serialization.boolean().optional(), "only-include-referenced-schemas": core.serialization.boolean().optional(), + "inline-path-parameters": core.serialization.boolean().optional(), }); export declare namespace OpenApiSettingsSchema { @@ -26,5 +27,6 @@ export declare namespace OpenApiSettingsSchema { "object-query-parameters"?: boolean | null; "respect-readonly-schemas"?: boolean | null; "only-include-referenced-schemas"?: boolean | null; + "inline-path-parameters"?: boolean | null; } } diff --git a/packages/cli/dynamic-snippets/src/__test__/test-definitions/path-parameters.json b/packages/cli/dynamic-snippets/src/__test__/test-definitions/path-parameters.json new file mode 100644 index 00000000000..f629bfd3619 --- /dev/null +++ b/packages/cli/dynamic-snippets/src/__test__/test-definitions/path-parameters.json @@ -0,0 +1,620 @@ +{ + "version": "1.0.0", + "types": { + "type_user:User": { + "type": "object", + "declaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "typeReference": { + "_type": "primitive", + "value": "STRING" + } + }, + { + "name": { + "name": { + "originalName": "tags", + "camelCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "snakeCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "screamingSnakeCase": { + "unsafeName": "TAGS", + "safeName": "TAGS" + }, + "pascalCase": { + "unsafeName": "Tags", + "safeName": "Tags" + } + }, + "wireValue": "tags" + }, + "typeReference": { + "_type": "list", + "value": { + "_type": "primitive", + "value": "STRING" + } + } + } + ] + } + }, + "headers": [], + "endpoints": { + "endpoint_user.getOrganization": { + "auth": null, + "declaration": { + "name": { + "originalName": "getOrganization", + "camelCase": { + "unsafeName": "getOrganization", + "safeName": "getOrganization" + }, + "snakeCase": { + "unsafeName": "get_organization", + "safeName": "get_organization" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION", + "safeName": "GET_ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "GetOrganization", + "safeName": "GetOrganization" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "location": { + "method": "GET", + "path": "/user/organizations/{organizationId}" + }, + "request": { + "type": "body", + "pathParameters": [ + { + "name": { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "wireValue": "organizationId" + }, + "typeReference": { + "_type": "primitive", + "value": "STRING" + } + } + ], + "body": null + }, + "response": { + "type": "json" + } + }, + "endpoint_user.getUser": { + "auth": null, + "declaration": { + "name": { + "originalName": "getUser", + "camelCase": { + "unsafeName": "getUser", + "safeName": "getUser" + }, + "snakeCase": { + "unsafeName": "get_user", + "safeName": "get_user" + }, + "screamingSnakeCase": { + "unsafeName": "GET_USER", + "safeName": "GET_USER" + }, + "pascalCase": { + "unsafeName": "GetUser", + "safeName": "GetUser" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "location": { + "method": "GET", + "path": "/user/users/{userId}" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "GetUsersRequest", + "camelCase": { + "unsafeName": "getUsersRequest", + "safeName": "getUsersRequest" + }, + "snakeCase": { + "unsafeName": "get_users_request", + "safeName": "get_users_request" + }, + "screamingSnakeCase": { + "unsafeName": "GET_USERS_REQUEST", + "safeName": "GET_USERS_REQUEST" + }, + "pascalCase": { + "unsafeName": "GetUsersRequest", + "safeName": "GetUsersRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "pathParameters": [ + { + "name": { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "wireValue": "userId" + }, + "typeReference": { + "_type": "primitive", + "value": "STRING" + } + } + ], + "queryParameters": [], + "headers": [], + "body": null + }, + "response": { + "type": "json" + } + }, + "endpoint_user.getOrganizationUser": { + "auth": null, + "declaration": { + "name": { + "originalName": "getOrganizationUser", + "camelCase": { + "unsafeName": "getOrganizationUser", + "safeName": "getOrganizationUser" + }, + "snakeCase": { + "unsafeName": "get_organization_user", + "safeName": "get_organization_user" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION_USER", + "safeName": "GET_ORGANIZATION_USER" + }, + "pascalCase": { + "unsafeName": "GetOrganizationUser", + "safeName": "GetOrganizationUser" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "location": { + "method": "GET", + "path": "/user/organizations/{organizationId}/users/{userId}" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "GetOrganizationUserRequest", + "camelCase": { + "unsafeName": "getOrganizationUserRequest", + "safeName": "getOrganizationUserRequest" + }, + "snakeCase": { + "unsafeName": "get_organization_user_request", + "safeName": "get_organization_user_request" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION_USER_REQUEST", + "safeName": "GET_ORGANIZATION_USER_REQUEST" + }, + "pascalCase": { + "unsafeName": "GetOrganizationUserRequest", + "safeName": "GetOrganizationUserRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "pathParameters": [ + { + "name": { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "wireValue": "organizationId" + }, + "typeReference": { + "_type": "primitive", + "value": "STRING" + } + }, + { + "name": { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "wireValue": "userId" + }, + "typeReference": { + "_type": "primitive", + "value": "STRING" + } + } + ], + "queryParameters": [], + "headers": [], + "body": null + }, + "response": { + "type": "json" + } + } + } +} \ No newline at end of file diff --git a/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap b/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap index bc958586e67..f4f21c918f7 100644 --- a/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap +++ b/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap @@ -2,4 +2,4 @@ exports[`dependencies > correctly incorporates dependencies 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"dependent","camelCase":{"unsafeName":"dependent","safeName":"dependent"},"snakeCase":{"unsafeName":"dependent","safeName":"dependent"},"screamingSnakeCase":{"unsafeName":"DEPENDENT","safeName":"DEPENDENT"},"pascalCase":{"unsafeName":"Dependent","safeName":"Dependent"}},"apiDisplayName":null,"apiDocs":null,"auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[],"idempotencyHeaders":[],"types":{"type_imported:RootString":{"inline":false,"name":{"name":{"originalName":"RootString","camelCase":{"unsafeName":"rootString","safeName":"rootString"},"snakeCase":{"unsafeName":"root_string","safeName":"root_string"},"screamingSnakeCase":{"unsafeName":"ROOT_STRING","safeName":"ROOT_STRING"},"pascalCase":{"unsafeName":"RootString","safeName":"RootString"}},"fernFilepath":{"allParts":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"packagePath":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"file":null},"typeId":"type_imported:RootString"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_y:IntegerAlias":{"inline":false,"name":{"name":{"originalName":"IntegerAlias","camelCase":{"unsafeName":"integerAlias","safeName":"integerAlias"},"snakeCase":{"unsafeName":"integer_alias","safeName":"integer_alias"},"screamingSnakeCase":{"unsafeName":"INTEGER_ALIAS","safeName":"INTEGER_ALIAS"},"pascalCase":{"unsafeName":"IntegerAlias","safeName":"IntegerAlias"}},"fernFilepath":{"allParts":[{"originalName":"y","camelCase":{"unsafeName":"y","safeName":"y"},"snakeCase":{"unsafeName":"y","safeName":"y"},"screamingSnakeCase":{"unsafeName":"Y","safeName":"Y"},"pascalCase":{"unsafeName":"Y","safeName":"Y"}}],"packagePath":[],"file":{"originalName":"y","camelCase":{"unsafeName":"y","safeName":"y"},"snakeCase":{"unsafeName":"y","safeName":"y"},"screamingSnakeCase":{"unsafeName":"Y","safeName":"Y"},"pascalCase":{"unsafeName":"Y","safeName":"Y"}}},"typeId":"type_y:IntegerAlias"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imported/x:StringAlias":{"inline":false,"name":{"name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}},{"originalName":"x","camelCase":{"unsafeName":"x","safeName":"x"},"snakeCase":{"unsafeName":"x","safeName":"x"},"screamingSnakeCase":{"unsafeName":"X","safeName":"X"},"pascalCase":{"unsafeName":"X","safeName":"X"}}],"packagePath":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"file":{"originalName":"x","camelCase":{"unsafeName":"x","safeName":"x"},"snakeCase":{"unsafeName":"x","safeName":"x"},"screamingSnakeCase":{"unsafeName":"X","safeName":"X"},"pascalCase":{"unsafeName":"X","safeName":"X"}}},"typeId":"type_imported/x:StringAlias"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null}},"errors":{},"services":{"service_imported":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"packagePath":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"file":null}},"displayName":null,"basePath":{"head":"/","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_imported.rootEndpoint","name":{"originalName":"rootEndpoint","camelCase":{"unsafeName":"rootEndpoint","safeName":"rootEndpoint"},"snakeCase":{"unsafeName":"root_endpoint","safeName":"root_endpoint"},"screamingSnakeCase":{"unsafeName":"ROOT_ENDPOINT","safeName":"ROOT_ENDPOINT"},"pascalCase":{"unsafeName":"RootEndpoint","safeName":"RootEndpoint"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"GET","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"","parts":[]},"pathParameters":[],"allPathParameters":[],"queryParameters":[],"headers":[],"requestBody":null,"sdkRequest":null,"response":{"body":null,"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[{"example":{"id":"0151c4f21dcb59ded0264beca4e001f6c9a6203a","url":"","name":null,"endpointHeaders":[],"endpointPathParameters":[],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[],"request":null,"response":{"type":"ok","value":{"type":"body","value":null}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"statusCode"},"basePath":null,"pathParameters":[],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{},"sharedTypes":["type_imported:RootString","type_y:IntegerAlias","type_imported/x:StringAlias"]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_imported":{"name":{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}},"fernFilepath":{"allParts":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"packagePath":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"file":null},"service":"service_imported","types":["type_imported:RootString"],"errors":[],"subpackages":["subpackage_imported/x"],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null},"subpackage_y":{"name":{"originalName":"y","camelCase":{"unsafeName":"y","safeName":"y"},"snakeCase":{"unsafeName":"y","safeName":"y"},"screamingSnakeCase":{"unsafeName":"Y","safeName":"Y"},"pascalCase":{"unsafeName":"Y","safeName":"Y"}},"fernFilepath":{"allParts":[{"originalName":"y","camelCase":{"unsafeName":"y","safeName":"y"},"snakeCase":{"unsafeName":"y","safeName":"y"},"screamingSnakeCase":{"unsafeName":"Y","safeName":"Y"},"pascalCase":{"unsafeName":"Y","safeName":"Y"}}],"packagePath":[],"file":{"originalName":"y","camelCase":{"unsafeName":"y","safeName":"y"},"snakeCase":{"unsafeName":"y","safeName":"y"},"screamingSnakeCase":{"unsafeName":"Y","safeName":"Y"},"pascalCase":{"unsafeName":"Y","safeName":"Y"}}},"service":null,"types":["type_y:IntegerAlias"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_imported/x":{"name":{"originalName":"x","camelCase":{"unsafeName":"x","safeName":"x"},"snakeCase":{"unsafeName":"x","safeName":"x"},"screamingSnakeCase":{"unsafeName":"X","safeName":"X"},"pascalCase":{"unsafeName":"X","safeName":"X"}},"fernFilepath":{"allParts":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}},{"originalName":"x","camelCase":{"unsafeName":"x","safeName":"x"},"snakeCase":{"unsafeName":"x","safeName":"x"},"screamingSnakeCase":{"unsafeName":"X","safeName":"X"},"pascalCase":{"unsafeName":"X","safeName":"X"}}],"packagePath":[{"originalName":"imported","camelCase":{"unsafeName":"imported","safeName":"imported"},"snakeCase":{"unsafeName":"imported","safeName":"imported"},"screamingSnakeCase":{"unsafeName":"IMPORTED","safeName":"IMPORTED"},"pascalCase":{"unsafeName":"Imported","safeName":"Imported"}}],"file":{"originalName":"x","camelCase":{"unsafeName":"x","safeName":"x"},"snakeCase":{"unsafeName":"x","safeName":"x"},"screamingSnakeCase":{"unsafeName":"X","safeName":"X"},"pascalCase":{"unsafeName":"X","safeName":"X"}}},"service":null,"types":["type_imported/x:StringAlias"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_imported","subpackage_y"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; -exports[`dependencies > file dependencies 1`] = `559734`; +exports[`dependencies > file dependencies 1`] = `560076`; diff --git a/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap b/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap index 937ce1c0af5..2faaa3ec166 100644 --- a/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap +++ b/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap @@ -4,7 +4,7 @@ exports[`ir > {"name":"auth-header-prefix"} 1`] = `"{"fdrApiDefinitionId":null," exports[`ir > {"name":"extended-examples"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"api","camelCase":{"unsafeName":"api","safeName":"api"},"snakeCase":{"unsafeName":"api","safeName":"api"},"screamingSnakeCase":{"unsafeName":"API","safeName":"API"},"pascalCase":{"unsafeName":"Api","safeName":"Api"}},"apiDisplayName":"API","apiDocs":null,"auth":{"requirement":"ALL","schemes":[{"_type":"bearer","token":{"originalName":"token","camelCase":{"unsafeName":"token","safeName":"token"},"snakeCase":{"unsafeName":"token","safeName":"token"},"screamingSnakeCase":{"unsafeName":"TOKEN","safeName":"TOKEN"},"pascalCase":{"unsafeName":"Token","safeName":"Token"}},"tokenEnvVar":null,"docs":null}],"docs":null},"headers":[],"idempotencyHeaders":[],"types":{"type_app:GetAppResponse":{"inline":false,"name":{"name":{"originalName":"GetAppResponse","camelCase":{"unsafeName":"getAppResponse","safeName":"getAppResponse"},"snakeCase":{"unsafeName":"get_app_response","safeName":"get_app_response"},"screamingSnakeCase":{"unsafeName":"GET_APP_RESPONSE","safeName":"GET_APP_RESPONSE"},"pascalCase":{"unsafeName":"GetAppResponse","safeName":"GetAppResponse"}},"fernFilepath":{"allParts":[{"originalName":"app","camelCase":{"unsafeName":"app","safeName":"app"},"snakeCase":{"unsafeName":"app","safeName":"app"},"screamingSnakeCase":{"unsafeName":"APP","safeName":"APP"},"pascalCase":{"unsafeName":"App","safeName":"App"}}],"packagePath":[],"file":{"originalName":"app","camelCase":{"unsafeName":"app","safeName":"app"},"snakeCase":{"unsafeName":"app","safeName":"app"},"screamingSnakeCase":{"unsafeName":"APP","safeName":"APP"},"pascalCase":{"unsafeName":"App","safeName":"App"}}},"typeId":"type_app:GetAppResponse"},"shape":{"_type":"object","extends":[{"name":{"originalName":"Deployment","camelCase":{"unsafeName":"deployment","safeName":"deployment"},"snakeCase":{"unsafeName":"deployment","safeName":"deployment"},"screamingSnakeCase":{"unsafeName":"DEPLOYMENT","safeName":"DEPLOYMENT"},"pascalCase":{"unsafeName":"Deployment","safeName":"Deployment"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Deployment"}],"properties":[{"name":{"name":{"originalName":"property","camelCase":{"unsafeName":"property","safeName":"property"},"snakeCase":{"unsafeName":"property","safeName":"property"},"screamingSnakeCase":{"unsafeName":"PROPERTY","safeName":"PROPERTY"},"pascalCase":{"unsafeName":"Property","safeName":"Property"}},"wireValue":"property"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[{"name":{"name":{"originalName":"appId","camelCase":{"unsafeName":"appId","safeName":"appId"},"snakeCase":{"unsafeName":"app_id","safeName":"app_id"},"screamingSnakeCase":{"unsafeName":"APP_ID","safeName":"APP_ID"},"pascalCase":{"unsafeName":"AppId","safeName":"AppId"}},"wireValue":"appId"},"valueType":{"_type":"named","name":{"originalName":"AppId","camelCase":{"unsafeName":"appId","safeName":"appId"},"snakeCase":{"unsafeName":"app_id","safeName":"app_id"},"screamingSnakeCase":{"unsafeName":"APP_ID","safeName":"APP_ID"},"pascalCase":{"unsafeName":"AppId","safeName":"AppId"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:AppId","default":null,"inline":null},"availability":null,"docs":null}]},"referencedTypes":["type_commons:Deployment","type_commons:AppId"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"Example","camelCase":{"unsafeName":"example","safeName":"example"},"snakeCase":{"unsafeName":"example","safeName":"example"},"screamingSnakeCase":{"unsafeName":"EXAMPLE","safeName":"EXAMPLE"},"pascalCase":{"unsafeName":"Example","safeName":"Example"}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"appId","camelCase":{"unsafeName":"appId","safeName":"appId"},"snakeCase":{"unsafeName":"app_id","safeName":"app_id"},"screamingSnakeCase":{"unsafeName":"APP_ID","safeName":"APP_ID"},"pascalCase":{"unsafeName":"AppId","safeName":"AppId"}},"wireValue":"appId"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_commons:AppId","fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"name":{"originalName":"AppId","camelCase":{"unsafeName":"appId","safeName":"appId"},"snakeCase":{"unsafeName":"app_id","safeName":"app_id"},"screamingSnakeCase":{"unsafeName":"APP_ID","safeName":"APP_ID"},"pascalCase":{"unsafeName":"AppId","safeName":"AppId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"your-app-id"}}},"jsonExample":"your-app-id"}}},"jsonExample":"your-app-id"},"originalTypeDeclaration":{"name":{"originalName":"Deployment","camelCase":{"unsafeName":"deployment","safeName":"deployment"},"snakeCase":{"unsafeName":"deployment","safeName":"deployment"},"screamingSnakeCase":{"unsafeName":"DEPLOYMENT","safeName":"DEPLOYMENT"},"pascalCase":{"unsafeName":"Deployment","safeName":"Deployment"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Deployment"}},{"name":{"name":{"originalName":"property","camelCase":{"unsafeName":"property","safeName":"property"},"snakeCase":{"unsafeName":"property","safeName":"property"},"screamingSnakeCase":{"unsafeName":"PROPERTY","safeName":"PROPERTY"},"pascalCase":{"unsafeName":"Property","safeName":"Property"}},"wireValue":"property"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"app-secret"}}},"jsonExample":"app-secret"},"originalTypeDeclaration":{"name":{"originalName":"GetAppResponse","camelCase":{"unsafeName":"getAppResponse","safeName":"getAppResponse"},"snakeCase":{"unsafeName":"get_app_response","safeName":"get_app_response"},"screamingSnakeCase":{"unsafeName":"GET_APP_RESPONSE","safeName":"GET_APP_RESPONSE"},"pascalCase":{"unsafeName":"GetAppResponse","safeName":"GetAppResponse"}},"fernFilepath":{"allParts":[{"originalName":"app","camelCase":{"unsafeName":"app","safeName":"app"},"snakeCase":{"unsafeName":"app","safeName":"app"},"screamingSnakeCase":{"unsafeName":"APP","safeName":"APP"},"pascalCase":{"unsafeName":"App","safeName":"App"}}],"packagePath":[],"file":{"originalName":"app","camelCase":{"unsafeName":"app","safeName":"app"},"snakeCase":{"unsafeName":"app","safeName":"app"},"screamingSnakeCase":{"unsafeName":"APP","safeName":"APP"},"pascalCase":{"unsafeName":"App","safeName":"App"}}},"typeId":"type_app:GetAppResponse"}}]},"jsonExample":{"appId":"your-app-id","property":"app-secret"},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_commons:AppId":{"inline":false,"name":{"name":{"originalName":"AppId","camelCase":{"unsafeName":"appId","safeName":"appId"},"snakeCase":{"unsafeName":"app_id","safeName":"app_id"},"screamingSnakeCase":{"unsafeName":"APP_ID","safeName":"APP_ID"},"pascalCase":{"unsafeName":"AppId","safeName":"AppId"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:AppId"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"Example","camelCase":{"unsafeName":"example","safeName":"example"},"snakeCase":{"unsafeName":"example","safeName":"example"},"screamingSnakeCase":{"unsafeName":"EXAMPLE","safeName":"EXAMPLE"},"pascalCase":{"unsafeName":"Example","safeName":"Example"}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"your-app-id"}}},"jsonExample":"your-app-id"}},"jsonExample":"your-app-id","docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_commons:Deployment":{"inline":false,"name":{"name":{"originalName":"Deployment","camelCase":{"unsafeName":"deployment","safeName":"deployment"},"snakeCase":{"unsafeName":"deployment","safeName":"deployment"},"screamingSnakeCase":{"unsafeName":"DEPLOYMENT","safeName":"DEPLOYMENT"},"pascalCase":{"unsafeName":"Deployment","safeName":"Deployment"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Deployment"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"appId","camelCase":{"unsafeName":"appId","safeName":"appId"},"snakeCase":{"unsafeName":"app_id","safeName":"app_id"},"screamingSnakeCase":{"unsafeName":"APP_ID","safeName":"APP_ID"},"pascalCase":{"unsafeName":"AppId","safeName":"AppId"}},"wireValue":"appId"},"valueType":{"_type":"named","name":{"originalName":"AppId","camelCase":{"unsafeName":"appId","safeName":"appId"},"snakeCase":{"unsafeName":"app_id","safeName":"app_id"},"screamingSnakeCase":{"unsafeName":"APP_ID","safeName":"APP_ID"},"pascalCase":{"unsafeName":"AppId","safeName":"AppId"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:AppId","default":null,"inline":null},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":["type_commons:AppId"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null}},"errors":{},"services":{},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"statusCode"},"basePath":null,"pathParameters":[],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{},"sharedTypes":["type_app:GetAppResponse","type_commons:AppId","type_commons:Deployment"]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_app":{"name":{"originalName":"app","camelCase":{"unsafeName":"app","safeName":"app"},"snakeCase":{"unsafeName":"app","safeName":"app"},"screamingSnakeCase":{"unsafeName":"APP","safeName":"APP"},"pascalCase":{"unsafeName":"App","safeName":"App"}},"fernFilepath":{"allParts":[{"originalName":"app","camelCase":{"unsafeName":"app","safeName":"app"},"snakeCase":{"unsafeName":"app","safeName":"app"},"screamingSnakeCase":{"unsafeName":"APP","safeName":"APP"},"pascalCase":{"unsafeName":"App","safeName":"App"}}],"packagePath":[],"file":{"originalName":"app","camelCase":{"unsafeName":"app","safeName":"app"},"snakeCase":{"unsafeName":"app","safeName":"app"},"screamingSnakeCase":{"unsafeName":"APP","safeName":"APP"},"pascalCase":{"unsafeName":"App","safeName":"App"}}},"service":null,"types":["type_app:GetAppResponse"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_commons":{"name":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"service":null,"types":["type_commons:AppId","type_commons:Deployment"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_app","subpackage_commons"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":false,"docs":null},"sdkConfig":{"isAuthMandatory":true,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; -exports[`ir > {"name":"file-upload"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":null,"auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[],"idempotencyHeaders":[],"types":{},"errors":{},"services":{"service_file-upload":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}],"packagePath":[],"file":{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_file-upload.fileUpload","name":{"originalName":"fileUpload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"/movies","parts":[]},"pathParameters":[],"allPathParameters":[],"queryParameters":[],"headers":[],"requestBody":{"type":"fileUpload","name":{"originalName":"FileUploadRequest","camelCase":{"unsafeName":"fileUploadRequest","safeName":"fileUploadRequest"},"snakeCase":{"unsafeName":"file_upload_request","safeName":"file_upload_request"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD_REQUEST","safeName":"FILE_UPLOAD_REQUEST"},"pascalCase":{"unsafeName":"FileUploadRequest","safeName":"FileUploadRequest"}},"properties":[{"type":"bodyProperty","contentType":null,"name":{"name":{"originalName":"foo","camelCase":{"unsafeName":"foo","safeName":"foo"},"snakeCase":{"unsafeName":"foo","safeName":"foo"},"screamingSnakeCase":{"unsafeName":"FOO","safeName":"FOO"},"pascalCase":{"unsafeName":"Foo","safeName":"Foo"}},"wireValue":"foo"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"type":"file","value":{"type":"file","key":{"name":{"originalName":"file","camelCase":{"unsafeName":"file","safeName":"file"},"snakeCase":{"unsafeName":"file","safeName":"file"},"screamingSnakeCase":{"unsafeName":"FILE","safeName":"FILE"},"pascalCase":{"unsafeName":"File","safeName":"File"}},"wireValue":"file"},"isOptional":false,"contentType":null}},{"type":"file","value":{"type":"file","key":{"name":{"originalName":"optionalFile","camelCase":{"unsafeName":"optionalFile","safeName":"optionalFile"},"snakeCase":{"unsafeName":"optional_file","safeName":"optional_file"},"screamingSnakeCase":{"unsafeName":"OPTIONAL_FILE","safeName":"OPTIONAL_FILE"},"pascalCase":{"unsafeName":"OptionalFile","safeName":"OptionalFile"}},"wireValue":"optionalFile"},"isOptional":true,"contentType":null}},{"type":"bodyProperty","contentType":null,"name":{"name":{"originalName":"bar","camelCase":{"unsafeName":"bar","safeName":"bar"},"snakeCase":{"unsafeName":"bar","safeName":"bar"},"screamingSnakeCase":{"unsafeName":"BAR","safeName":"BAR"},"pascalCase":{"unsafeName":"Bar","safeName":"Bar"}},"wireValue":"bar"},"valueType":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}},"availability":null,"docs":null}],"docs":null},"sdkRequest":{"shape":{"type":"wrapper","wrapperName":{"originalName":"FileUploadRequest","camelCase":{"unsafeName":"fileUploadRequest","safeName":"fileUploadRequest"},"snakeCase":{"unsafeName":"file_upload_request","safeName":"file_upload_request"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD_REQUEST","safeName":"FILE_UPLOAD_REQUEST"},"pascalCase":{"unsafeName":"FileUploadRequest","safeName":"FileUploadRequest"}},"bodyKey":{"originalName":"body","camelCase":{"unsafeName":"body","safeName":"body"},"snakeCase":{"unsafeName":"body","safeName":"body"},"screamingSnakeCase":{"unsafeName":"BODY","safeName":"BODY"},"pascalCase":{"unsafeName":"Body","safeName":"Body"}}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":null,"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"statusCode"},"basePath":null,"pathParameters":[],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{},"sharedTypes":[]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_file-upload":{"name":{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}},"fernFilepath":{"allParts":[{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}],"packagePath":[],"file":{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}},"service":"service_file-upload","types":[],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_file-upload"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; +exports[`ir > {"name":"file-upload"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":null,"auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[],"idempotencyHeaders":[],"types":{},"errors":{},"services":{"service_file-upload":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}],"packagePath":[],"file":{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_file-upload.fileUpload","name":{"originalName":"fileUpload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"/movies","parts":[]},"pathParameters":[],"allPathParameters":[],"queryParameters":[],"headers":[],"requestBody":{"type":"fileUpload","name":{"originalName":"FileUploadRequest","camelCase":{"unsafeName":"fileUploadRequest","safeName":"fileUploadRequest"},"snakeCase":{"unsafeName":"file_upload_request","safeName":"file_upload_request"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD_REQUEST","safeName":"FILE_UPLOAD_REQUEST"},"pascalCase":{"unsafeName":"FileUploadRequest","safeName":"FileUploadRequest"}},"properties":[{"type":"bodyProperty","contentType":null,"name":{"name":{"originalName":"foo","camelCase":{"unsafeName":"foo","safeName":"foo"},"snakeCase":{"unsafeName":"foo","safeName":"foo"},"screamingSnakeCase":{"unsafeName":"FOO","safeName":"FOO"},"pascalCase":{"unsafeName":"Foo","safeName":"Foo"}},"wireValue":"foo"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"type":"file","value":{"type":"file","key":{"name":{"originalName":"file","camelCase":{"unsafeName":"file","safeName":"file"},"snakeCase":{"unsafeName":"file","safeName":"file"},"screamingSnakeCase":{"unsafeName":"FILE","safeName":"FILE"},"pascalCase":{"unsafeName":"File","safeName":"File"}},"wireValue":"file"},"isOptional":false,"contentType":null}},{"type":"file","value":{"type":"file","key":{"name":{"originalName":"optionalFile","camelCase":{"unsafeName":"optionalFile","safeName":"optionalFile"},"snakeCase":{"unsafeName":"optional_file","safeName":"optional_file"},"screamingSnakeCase":{"unsafeName":"OPTIONAL_FILE","safeName":"OPTIONAL_FILE"},"pascalCase":{"unsafeName":"OptionalFile","safeName":"OptionalFile"}},"wireValue":"optionalFile"},"isOptional":true,"contentType":null}},{"type":"bodyProperty","contentType":null,"name":{"name":{"originalName":"bar","camelCase":{"unsafeName":"bar","safeName":"bar"},"snakeCase":{"unsafeName":"bar","safeName":"bar"},"screamingSnakeCase":{"unsafeName":"BAR","safeName":"BAR"},"pascalCase":{"unsafeName":"Bar","safeName":"Bar"}},"wireValue":"bar"},"valueType":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}},"availability":null,"docs":null}],"docs":null},"sdkRequest":{"shape":{"type":"wrapper","wrapperName":{"originalName":"FileUploadRequest","camelCase":{"unsafeName":"fileUploadRequest","safeName":"fileUploadRequest"},"snakeCase":{"unsafeName":"file_upload_request","safeName":"file_upload_request"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD_REQUEST","safeName":"FILE_UPLOAD_REQUEST"},"pascalCase":{"unsafeName":"FileUploadRequest","safeName":"FileUploadRequest"}},"bodyKey":{"originalName":"body","camelCase":{"unsafeName":"body","safeName":"body"},"snakeCase":{"unsafeName":"body","safeName":"body"},"screamingSnakeCase":{"unsafeName":"BODY","safeName":"BODY"},"pascalCase":{"unsafeName":"Body","safeName":"Body"}},"includePathParameters":false,"onlyPathParameters":false},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":null,"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"statusCode"},"basePath":null,"pathParameters":[],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{},"sharedTypes":[]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_file-upload":{"name":{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}},"fernFilepath":{"allParts":[{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}],"packagePath":[],"file":{"originalName":"file-upload","camelCase":{"unsafeName":"fileUpload","safeName":"fileUpload"},"snakeCase":{"unsafeName":"file_upload","safeName":"file_upload"},"screamingSnakeCase":{"unsafeName":"FILE_UPLOAD","safeName":"FILE_UPLOAD"},"pascalCase":{"unsafeName":"FileUpload","safeName":"FileUpload"}}},"service":"service_file-upload","types":[],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_file-upload"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; exports[`ir > {"name":"migration","version":"v1"} 1`] = `"{"apiName":"my-api","auth":{"requirement":"ALL","schemes":[]},"headers":[{"name":{"originalValue":"apiVersion","camelCase":"apiVersion","pascalCase":"ApiVersion","snakeCase":"api_version","screamingSnakeCase":"API_VERSION","wireValue":"X-API-VERSION"},"nameV2":{"wireValue":"X-API-VERSION","name":{"safeName":{"originalValue":"apiVersion","camelCase":"apiVersion","pascalCase":"ApiVersion","snakeCase":"api_version","screamingSnakeCase":"API_VERSION"},"unsafeName":{"originalValue":"apiVersion","camelCase":"apiVersion","pascalCase":"ApiVersion","snakeCase":"api_version","screamingSnakeCase":"API_VERSION"}}},"valueType":{"container":{"optional":{"primitive":"STRING","_type":"primitive"},"_type":"optional"},"_type":"container"},"availability":{"status":"GENERAL_AVAILABILITY"}}],"types":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}},"shape":{"extends":[],"properties":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"name","camelCase":"name","pascalCase":"Name","snakeCase":"name","screamingSnakeCase":"NAME","wireValue":"name"},"nameV2":{"wireValue":"name","name":{"safeName":{"originalValue":"name","camelCase":"name","pascalCase":"Name","snakeCase":"name","screamingSnakeCase":"NAME"},"unsafeName":{"originalValue":"name","camelCase":"name","pascalCase":"Name","snakeCase":"name","screamingSnakeCase":"NAME"}}},"valueType":{"primitive":"STRING","_type":"primitive"}},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE","wireValue":"age"},"nameV2":{"wireValue":"age","name":{"safeName":{"originalValue":"age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}},"valueType":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}},"_type":"named"}}],"_type":"object"},"examples":[{"properties":[{"wireKey":"name","value":{"primitive":{"string":"George the Director","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}}},{"wireKey":"age","value":{"typeName":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}},"shape":{"value":{"primitive":{"integer":20,"type":"integer"},"type":"primitive"},"type":"alias"},"type":"named"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}}}],"type":"object"}],"referencedTypes":[{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}}]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}},"shape":{"aliasOf":{"primitive":"INTEGER","_type":"primitive"},"resolvedType":{"primitive":"INTEGER","_type":"primitive"},"_type":"alias"},"examples":[{"value":{"primitive":{"integer":20,"type":"integer"},"type":"primitive"},"type":"alias"}],"referencedTypes":[]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"LiteralString","nameV2":{"originalValue":"LiteralString","camelCase":"literalString","pascalCase":"LiteralString","snakeCase":"literal_string","screamingSnakeCase":"LITERAL_STRING"},"nameV3":{"safeName":{"originalValue":"LiteralString","camelCase":"literalString","pascalCase":"LiteralString","snakeCase":"literal_string","screamingSnakeCase":"LITERAL_STRING"},"unsafeName":{"originalValue":"LiteralString","camelCase":"literalString","pascalCase":"LiteralString","snakeCase":"literal_string","screamingSnakeCase":"LITERAL_STRING"}}},"shape":{"aliasOf":{"container":{"literal":{"string":"hello","type":"string"},"_type":"literal"},"_type":"container"},"resolvedType":{"container":{"literal":{"string":"hello","type":"string"},"_type":"literal"},"_type":"container"},"_type":"alias"},"examples":[],"referencedTypes":[]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CurrencyAmount","nameV2":{"originalValue":"CurrencyAmount","camelCase":"currencyAmount","pascalCase":"CurrencyAmount","snakeCase":"currency_amount","screamingSnakeCase":"CURRENCY_AMOUNT"},"nameV3":{"safeName":{"originalValue":"CurrencyAmount","camelCase":"currencyAmount","pascalCase":"CurrencyAmount","snakeCase":"currency_amount","screamingSnakeCase":"CURRENCY_AMOUNT"},"unsafeName":{"originalValue":"CurrencyAmount","camelCase":"currencyAmount","pascalCase":"CurrencyAmount","snakeCase":"currency_amount","screamingSnakeCase":"CURRENCY_AMOUNT"}}},"shape":{"aliasOf":{"primitive":"STRING","_type":"primitive"},"resolvedType":{"primitive":"STRING","_type":"primitive"},"_type":"alias"},"examples":[{"value":{"primitive":{"string":"$4.50","type":"string"},"type":"primitive"},"type":"alias"}],"referencedTypes":[]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}}},"shape":{"aliasOf":{"primitive":"STRING","_type":"primitive"},"resolvedType":{"primitive":"STRING","_type":"primitive"},"_type":"alias"},"examples":[{"value":{"primitive":{"string":"id1","type":"string"},"type":"primitive"},"type":"alias"},{"value":{"primitive":{"string":"id2","type":"string"},"type":"primitive"},"type":"alias"}],"referencedTypes":[]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"ActorId","nameV2":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"nameV3":{"safeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"unsafeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"}}},"shape":{"aliasOf":{"primitive":"STRING","_type":"primitive"},"resolvedType":{"primitive":"STRING","_type":"primitive"},"_type":"alias"},"examples":[],"referencedTypes":[]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"Movie","nameV2":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"nameV3":{"safeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"unsafeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"}}},"shape":{"extends":[],"properties":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"id","camelCase":"id","pascalCase":"Id","snakeCase":"id","screamingSnakeCase":"ID","wireValue":"id"},"nameV2":{"wireValue":"id","name":{"safeName":{"originalValue":"id","camelCase":"id","pascalCase":"Id","snakeCase":"id","screamingSnakeCase":"ID"},"unsafeName":{"originalValue":"id","camelCase":"id","pascalCase":"Id","snakeCase":"id","screamingSnakeCase":"ID"}}},"valueType":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}},"_type":"named"}},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"title","camelCase":"title","pascalCase":"Title","snakeCase":"title","screamingSnakeCase":"TITLE","wireValue":"title"},"nameV2":{"wireValue":"title","name":{"safeName":{"originalValue":"title","camelCase":"title","pascalCase":"Title","snakeCase":"title","screamingSnakeCase":"TITLE"},"unsafeName":{"originalValue":"title","camelCase":"title","pascalCase":"Title","snakeCase":"title","screamingSnakeCase":"TITLE"}}},"valueType":{"primitive":"STRING","_type":"primitive"}},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"rating","camelCase":"rating","pascalCase":"Rating","snakeCase":"rating","screamingSnakeCase":"RATING","wireValue":"rating"},"nameV2":{"wireValue":"rating","name":{"safeName":{"originalValue":"rating","camelCase":"rating","pascalCase":"Rating","snakeCase":"rating","screamingSnakeCase":"RATING"},"unsafeName":{"originalValue":"rating","camelCase":"rating","pascalCase":"Rating","snakeCase":"rating","screamingSnakeCase":"RATING"}}},"valueType":{"primitive":"DOUBLE","_type":"primitive"}}],"_type":"object"},"examples":[{"properties":[{"wireKey":"id","value":{"typeName":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}}},"shape":{"value":{"primitive":{"string":"my-movie-id","type":"string"},"type":"primitive"},"type":"alias"},"type":"named"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"Movie","nameV2":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"nameV3":{"safeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"unsafeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"}}}},{"wireKey":"title","value":{"primitive":{"string":"Goodwill Hunting","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"Movie","nameV2":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"nameV3":{"safeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"unsafeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"}}}},{"wireKey":"rating","value":{"primitive":{"double":14.5,"type":"double"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"Movie","nameV2":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"nameV3":{"safeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"unsafeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"}}}}],"type":"object"}],"referencedTypes":[{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}}}]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}},"shape":{"extends":[],"properties":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"title","camelCase":"title","pascalCase":"Title","snakeCase":"title","screamingSnakeCase":"TITLE","wireValue":"title"},"nameV2":{"wireValue":"title","name":{"safeName":{"originalValue":"title","camelCase":"title","pascalCase":"Title","snakeCase":"title","screamingSnakeCase":"TITLE"},"unsafeName":{"originalValue":"title","camelCase":"title","pascalCase":"Title","snakeCase":"title","screamingSnakeCase":"TITLE"}}},"valueType":{"primitive":"STRING","_type":"primitive"}},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"ratings","camelCase":"ratings","pascalCase":"Ratings","snakeCase":"ratings","screamingSnakeCase":"RATINGS","wireValue":"ratings"},"nameV2":{"wireValue":"ratings","name":{"safeName":{"originalValue":"ratings","camelCase":"ratings","pascalCase":"Ratings","snakeCase":"ratings","screamingSnakeCase":"RATINGS"},"unsafeName":{"originalValue":"ratings","camelCase":"ratings","pascalCase":"Ratings","snakeCase":"ratings","screamingSnakeCase":"RATINGS"}}},"valueType":{"container":{"list":{"primitive":"DOUBLE","_type":"primitive"},"_type":"list"},"_type":"container"}}],"_type":"object"},"examples":[{"properties":[{"wireKey":"title","value":{"primitive":{"string":"Winnie the Pooh","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"ratings","value":{"container":{"list":[{"primitive":{"double":1,"type":"double"},"type":"primitive"},{"primitive":{"double":2,"type":"double"},"type":"primitive"},{"primitive":{"double":3,"type":"double"},"type":"primitive"}],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}}],"type":"object"}],"referencedTypes":[]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"DirectorWrapper","nameV2":{"originalValue":"DirectorWrapper","camelCase":"directorWrapper","pascalCase":"DirectorWrapper","snakeCase":"director_wrapper","screamingSnakeCase":"DIRECTOR_WRAPPER"},"nameV3":{"safeName":{"originalValue":"DirectorWrapper","camelCase":"directorWrapper","pascalCase":"DirectorWrapper","snakeCase":"director_wrapper","screamingSnakeCase":"DIRECTOR_WRAPPER"},"unsafeName":{"originalValue":"DirectorWrapper","camelCase":"directorWrapper","pascalCase":"DirectorWrapper","snakeCase":"director_wrapper","screamingSnakeCase":"DIRECTOR_WRAPPER"}}},"shape":{"extends":[],"properties":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR","wireValue":"director"},"nameV2":{"wireValue":"director","name":{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}},"valueType":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}},"_type":"named"}}],"_type":"object"},"examples":[{"properties":[{"wireKey":"director","value":{"typeName":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}},"shape":{"properties":[{"wireKey":"name","value":{"primitive":{"string":"George the Director","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}}},{"wireKey":"age","value":{"typeName":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}},"shape":{"value":{"primitive":{"integer":20,"type":"integer"},"type":"primitive"},"type":"alias"},"type":"named"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}}}],"type":"object"},"type":"named"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"DirectorWrapper","nameV2":{"originalValue":"DirectorWrapper","camelCase":"directorWrapper","pascalCase":"DirectorWrapper","snakeCase":"director_wrapper","screamingSnakeCase":"DIRECTOR_WRAPPER"},"nameV3":{"safeName":{"originalValue":"DirectorWrapper","camelCase":"directorWrapper","pascalCase":"DirectorWrapper","snakeCase":"director_wrapper","screamingSnakeCase":"DIRECTOR_WRAPPER"},"unsafeName":{"originalValue":"DirectorWrapper","camelCase":"directorWrapper","pascalCase":"DirectorWrapper","snakeCase":"director_wrapper","screamingSnakeCase":"DIRECTOR_WRAPPER"}}}}],"type":"object"}],"referencedTypes":[{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}},{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}}]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"EmptyObject","nameV2":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"nameV3":{"safeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"unsafeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"}}},"shape":{"extends":[],"properties":[],"_type":"object"},"examples":[],"referencedTypes":[]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"Person","nameV2":{"originalValue":"Person","camelCase":"person","pascalCase":"Person","snakeCase":"person","screamingSnakeCase":"PERSON"},"nameV3":{"safeName":{"originalValue":"Person","camelCase":"person","pascalCase":"Person","snakeCase":"person","screamingSnakeCase":"PERSON"},"unsafeName":{"originalValue":"Person","camelCase":"person","pascalCase":"Person","snakeCase":"person","screamingSnakeCase":"PERSON"}}},"shape":{"discriminant":"type","discriminantV2":{"originalValue":"type","camelCase":"type","pascalCase":"Type","snakeCase":"type","screamingSnakeCase":"TYPE","wireValue":"type"},"discriminantV3":{"wireValue":"type","name":{"safeName":{"originalValue":"type","camelCase":"type","pascalCase":"Type","snakeCase":"type","screamingSnakeCase":"TYPE"},"unsafeName":{"originalValue":"type","camelCase":"type","pascalCase":"Type","snakeCase":"type","screamingSnakeCase":"TYPE"}}},"types":[{"discriminantValue":{"originalValue":"actor","camelCase":"actor","pascalCase":"Actor","snakeCase":"actor","screamingSnakeCase":"ACTOR","wireValue":"actor"},"discriminantValueV2":{"wireValue":"actor","name":{"safeName":{"originalValue":"actor","camelCase":"actor","pascalCase":"Actor","snakeCase":"actor","screamingSnakeCase":"ACTOR"},"unsafeName":{"originalValue":"actor","camelCase":"actor","pascalCase":"Actor","snakeCase":"actor","screamingSnakeCase":"ACTOR"}}},"valueType":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"ActorId","nameV2":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"nameV3":{"safeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"unsafeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"}},"_type":"named"},"shape":{"name":{"originalValue":"value","camelCase":"value","pascalCase":"Value","snakeCase":"value","screamingSnakeCase":"VALUE","wireValue":"value"},"nameV2":{"wireValue":"value","name":{"safeName":{"originalValue":"value","camelCase":"value","pascalCase":"Value","snakeCase":"value","screamingSnakeCase":"VALUE"},"unsafeName":{"originalValue":"value","camelCase":"value","pascalCase":"Value","snakeCase":"value","screamingSnakeCase":"VALUE"}}},"type":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"ActorId","nameV2":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"nameV3":{"safeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"unsafeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"}},"_type":"named"},"_type":"singleProperty"}},{"discriminantValue":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR","wireValue":"director"},"discriminantValueV2":{"wireValue":"director","name":{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}},"valueType":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}},"_type":"named"},"shape":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}},"_type":"samePropertiesAsObject"}},{"discriminantValue":{"originalValue":"producer","camelCase":"producer","pascalCase":"Producer","snakeCase":"producer","screamingSnakeCase":"PRODUCER","wireValue":"producer"},"discriminantValueV2":{"wireValue":"producer","name":{"safeName":{"originalValue":"producer","camelCase":"producer","pascalCase":"Producer","snakeCase":"producer","screamingSnakeCase":"PRODUCER"},"unsafeName":{"originalValue":"producer","camelCase":"producer","pascalCase":"Producer","snakeCase":"producer","screamingSnakeCase":"PRODUCER"}}},"valueType":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"EmptyObject","nameV2":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"nameV3":{"safeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"unsafeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"}},"_type":"named"},"shape":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"EmptyObject","nameV2":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"nameV3":{"safeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"unsafeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"}},"_type":"samePropertiesAsObject"}},{"docs":"i am docs","discriminantValue":{"originalValue":"cinematographer","camelCase":"cinematographer","pascalCase":"Cinematographer","snakeCase":"cinematographer","screamingSnakeCase":"CINEMATOGRAPHER","wireValue":"cinematographer"},"discriminantValueV2":{"wireValue":"cinematographer","name":{"safeName":{"originalValue":"cinematographer","camelCase":"cinematographer","pascalCase":"Cinematographer","snakeCase":"cinematographer","screamingSnakeCase":"CINEMATOGRAPHER"},"unsafeName":{"originalValue":"cinematographer","camelCase":"cinematographer","pascalCase":"Cinematographer","snakeCase":"cinematographer","screamingSnakeCase":"CINEMATOGRAPHER"}}},"valueType":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"EmptyObject","nameV2":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"nameV3":{"safeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"unsafeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"}},"_type":"named"},"shape":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"EmptyObject","nameV2":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"nameV3":{"safeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"unsafeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"}},"_type":"samePropertiesAsObject"}}],"_type":"union"},"examples":[{"wireDiscriminantValue":"actor","properties":{"singleProperty":{"typeName":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"ActorId","nameV2":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"nameV3":{"safeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"unsafeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"}}},"shape":{"value":{"primitive":{"string":"Matt Damon","type":"string"},"type":"primitive"},"type":"alias"},"type":"named"},"type":"singleProperty"},"type":"union"},{"wireDiscriminantValue":"director","properties":{"typeName":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}},"shape":{"properties":[{"wireKey":"name","value":{"primitive":{"string":"George the Directory","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}}},{"wireKey":"age","value":{"typeName":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}},"shape":{"value":{"primitive":{"integer":100,"type":"integer"},"type":"primitive"},"type":"alias"},"type":"named"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}}}],"type":"object"},"type":"samePropertiesAsObject"},"type":"union"},{"wireDiscriminantValue":"producer","properties":{"typeName":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"EmptyObject","nameV2":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"nameV3":{"safeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"unsafeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"}}},"shape":{"properties":[],"type":"object"},"type":"samePropertiesAsObject"},"type":"union"}],"referencedTypes":[{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"ActorId","nameV2":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"nameV3":{"safeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"},"unsafeName":{"originalValue":"ActorId","camelCase":"actorId","pascalCase":"ActorId","snakeCase":"actor_id","screamingSnakeCase":"ACTOR_ID"}}},{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Director","nameV2":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"nameV3":{"safeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"Director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}},{"fernFilepath":[{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}],"fernFilepathV2":[{"safeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"},"unsafeName":{"originalValue":"director","camelCase":"director","pascalCase":"Director","snakeCase":"director","screamingSnakeCase":"DIRECTOR"}}],"name":"Age","nameV2":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"nameV3":{"safeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"},"unsafeName":{"originalValue":"Age","camelCase":"age","pascalCase":"Age","snakeCase":"age","screamingSnakeCase":"AGE"}}},{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"EmptyObject","nameV2":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"nameV3":{"safeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"},"unsafeName":{"originalValue":"EmptyObject","camelCase":"emptyObject","pascalCase":"EmptyObject","snakeCase":"empty_object","screamingSnakeCase":"EMPTY_OBJECT"}}}]},{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}},"shape":{"extends":[{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}],"properties":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"selfReferencing","camelCase":"selfReferencing","pascalCase":"SelfReferencing","snakeCase":"self_referencing","screamingSnakeCase":"SELF_REFERENCING","wireValue":"selfReferencing"},"nameV2":{"wireValue":"selfReferencing","name":{"safeName":{"originalValue":"selfReferencing","camelCase":"selfReferencing","pascalCase":"SelfReferencing","snakeCase":"self_referencing","screamingSnakeCase":"SELF_REFERENCING"},"unsafeName":{"originalValue":"selfReferencing","camelCase":"selfReferencing","pascalCase":"SelfReferencing","snakeCase":"self_referencing","screamingSnakeCase":"SELF_REFERENCING"}}},"valueType":{"container":{"list":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}},"_type":"named"},"_type":"list"},"_type":"container"}}],"_type":"object"},"examples":[{"properties":[{"wireKey":"title","value":{"primitive":{"string":"The Godfather","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"ratings","value":{"container":{"list":[{"primitive":{"double":10,"type":"double"},"type":"primitive"},{"primitive":{"double":5,"type":"double"},"type":"primitive"},{"primitive":{"double":9,"type":"double"},"type":"primitive"}],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"selfReferencing","value":{"container":{"list":[{"typeName":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}},"shape":{"properties":[{"wireKey":"title","value":{"primitive":{"string":"The Godfather II","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"ratings","value":{"container":{"list":[{"primitive":{"double":10,"type":"double"},"type":"primitive"},{"primitive":{"double":11,"type":"double"},"type":"primitive"}],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"selfReferencing","value":{"container":{"list":[],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}}}],"type":"object"},"type":"named"},{"typeName":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}},"shape":{"properties":[{"wireKey":"title","value":{"primitive":{"string":"The Godfather III","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"ratings","value":{"container":{"list":[],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"selfReferencing","value":{"container":{"list":[],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}}}],"type":"object"},"type":"named"}],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}}}],"type":"object"},{"properties":[{"wireKey":"title","value":{"primitive":{"string":"Goodfellas","type":"string"},"type":"primitive"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"ratings","value":{"container":{"list":[{"primitive":{"double":1,"type":"double"},"type":"primitive"},{"primitive":{"double":2,"type":"double"},"type":"primitive"},{"primitive":{"double":3,"type":"double"},"type":"primitive"}],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}}},{"wireKey":"selfReferencing","value":{"container":{"list":[],"type":"list"},"type":"container"},"originalTypeDeclaration":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}}}],"type":"object"}],"referencedTypes":[{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}}},{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"RecursiveType","nameV2":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"nameV3":{"safeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"},"unsafeName":{"originalValue":"RecursiveType","camelCase":"recursiveType","pascalCase":"RecursiveType","snakeCase":"recursive_type","screamingSnakeCase":"RECURSIVE_TYPE"}}}]}],"services":{"websocket":[],"http":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"name":"ImdbService","fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}]},"basePath":"/movies","basePathV2":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"endpoints":[{"availability":{"status":"GENERAL_AVAILABILITY"},"id":"createMovie","name":{"originalValue":"createMovie","camelCase":"createMovie","pascalCase":"CreateMovie","snakeCase":"create_movie","screamingSnakeCase":"CREATE_MOVIE"},"nameV2":{"safeName":{"originalValue":"createMovie","camelCase":"createMovie","pascalCase":"CreateMovie","snakeCase":"create_movie","screamingSnakeCase":"CREATE_MOVIE"},"unsafeName":{"originalValue":"createMovie","camelCase":"createMovie","pascalCase":"CreateMovie","snakeCase":"create_movie","screamingSnakeCase":"CREATE_MOVIE"}},"method":"POST","headers":[],"path":{"head":"","parts":[]},"pathParameters":[],"queryParameters":[],"request":{"type":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}},"_type":"named"},"typeV2":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"CreateMovieRequest","nameV2":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"nameV3":{"safeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"},"unsafeName":{"originalValue":"CreateMovieRequest","camelCase":"createMovieRequest","pascalCase":"CreateMovieRequest","snakeCase":"create_movie_request","screamingSnakeCase":"CREATE_MOVIE_REQUEST"}},"_type":"named"}},"response":{"type":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}},"_type":"named"},"typeV2":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}},"_type":"named"}},"errors":[{"error":{"name":"BadRequestError","nameV2":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"nameV3":{"safeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"unsafeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"}},"fernFilepath":[{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}],"fernFilepathV2":[{"safeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"},"unsafeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}}]}}],"errorsV2":{"discriminant":{"originalValue":"errorName","camelCase":"errorName","snakeCase":"error_name","pascalCase":"ErrorName","screamingSnakeCase":"ERROR_NAME","wireValue":"errorName"},"types":[{"discriminantValue":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR","wireValue":"BadRequestError"},"shape":{"type":"noProperties"}}]},"auth":false},{"availability":{"status":"GENERAL_AVAILABILITY"},"id":"getMovie","name":{"originalValue":"getMovie","camelCase":"getMovie","pascalCase":"GetMovie","snakeCase":"get_movie","screamingSnakeCase":"GET_MOVIE"},"nameV2":{"safeName":{"originalValue":"getMovie","camelCase":"getMovie","pascalCase":"GetMovie","snakeCase":"get_movie","screamingSnakeCase":"GET_MOVIE"},"unsafeName":{"originalValue":"getMovie","camelCase":"getMovie","pascalCase":"GetMovie","snakeCase":"get_movie","screamingSnakeCase":"GET_MOVIE"}},"method":"GET","headers":[],"path":{"head":"/","parts":[{"pathParameter":"movieId","tail":""}]},"pathParameters":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"movieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV2":{"safeName":{"originalValue":"movieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"movieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}},"valueType":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}},"_type":"named"}}],"queryParameters":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"movieName","camelCase":"movieName","pascalCase":"MovieName","snakeCase":"movie_name","screamingSnakeCase":"MOVIE_NAME","wireValue":"movieName"},"nameV2":{"wireValue":"movieName","name":{"safeName":{"originalValue":"movieName","camelCase":"movieName","pascalCase":"MovieName","snakeCase":"movie_name","screamingSnakeCase":"MOVIE_NAME"},"unsafeName":{"originalValue":"movieName","camelCase":"movieName","pascalCase":"MovieName","snakeCase":"movie_name","screamingSnakeCase":"MOVIE_NAME"}}},"valueType":{"primitive":"STRING","_type":"primitive"},"allowMultiple":true}],"request":{"type":{"_type":"void"}},"response":{"type":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"Movie","nameV2":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"nameV3":{"safeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"unsafeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"}},"_type":"named"},"typeV2":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"Movie","nameV2":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"nameV3":{"safeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"},"unsafeName":{"originalValue":"Movie","camelCase":"movie","pascalCase":"Movie","snakeCase":"movie","screamingSnakeCase":"MOVIE"}},"_type":"named"}},"errors":[{"error":{"name":"NotFoundError","nameV2":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"nameV3":{"safeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"unsafeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"}},"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}]}},{"error":{"name":"BadRequestError","nameV2":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"nameV3":{"safeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"unsafeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"}},"fernFilepath":[{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}],"fernFilepathV2":[{"safeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"},"unsafeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}}]}}],"errorsV2":{"discriminant":{"originalValue":"errorName","camelCase":"errorName","snakeCase":"error_name","pascalCase":"ErrorName","screamingSnakeCase":"ERROR_NAME","wireValue":"errorName"},"types":[{"discriminantValue":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR","wireValue":"NotFoundError"},"shape":{"name":{"originalValue":"content","camelCase":"content","snakeCase":"content","pascalCase":"Content","screamingSnakeCase":"CONTENT","wireValue":"content"},"error":{"name":"NotFoundError","nameV2":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"nameV3":{"safeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"unsafeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"}},"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}]},"type":"singleProperty"}},{"discriminantValue":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR","wireValue":"BadRequestError"},"shape":{"type":"noProperties"}}]},"auth":false},{"availability":{"status":"GENERAL_AVAILABILITY"},"id":"delete","name":{"originalValue":"delete","camelCase":"delete","pascalCase":"Delete","snakeCase":"delete","screamingSnakeCase":"DELETE"},"nameV2":{"safeName":{"originalValue":"delete","camelCase":"delete","pascalCase":"Delete","snakeCase":"delete","screamingSnakeCase":"DELETE"},"unsafeName":{"originalValue":"delete","camelCase":"delete","pascalCase":"Delete","snakeCase":"delete","screamingSnakeCase":"DELETE"}},"method":"DELETE","headers":[],"path":{"head":"/","parts":[{"pathParameter":"movieId","tail":""}]},"pathParameters":[{"availability":{"status":"GENERAL_AVAILABILITY"},"name":{"originalValue":"movieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV2":{"safeName":{"originalValue":"movieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"movieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}},"valueType":{"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}],"name":"MovieId","nameV2":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"nameV3":{"safeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"},"unsafeName":{"originalValue":"MovieId","camelCase":"movieId","pascalCase":"MovieId","snakeCase":"movie_id","screamingSnakeCase":"MOVIE_ID"}},"_type":"named"}}],"queryParameters":[],"request":{"type":{"_type":"void"}},"response":{"type":{"_type":"void"}},"errors":[{"error":{"name":"BadRequestError","nameV2":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"nameV3":{"safeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"unsafeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"}},"fernFilepath":[{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}],"fernFilepathV2":[{"safeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"},"unsafeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}}]}}],"errorsV2":{"discriminant":{"originalValue":"errorName","camelCase":"errorName","snakeCase":"error_name","pascalCase":"ErrorName","screamingSnakeCase":"ERROR_NAME","wireValue":"errorName"},"types":[{"discriminantValue":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR","wireValue":"BadRequestError"},"shape":{"type":"noProperties"}}]},"auth":false}]}]},"constants":{"errorDiscriminant":"_error","errorInstanceIdKey":"_errorInstanceId","unknownErrorDiscriminantValue":"_unknown"},"constantsV2":{"errors":{"errorInstanceIdKey":{"originalValue":"errorInstanceId","camelCase":"errorInstanceId","pascalCase":"ErrorInstanceId","snakeCase":"error_instance_id","screamingSnakeCase":"ERROR_INSTANCE_ID","wireValue":"errorInstanceId"},"errorDiscriminant":{"originalValue":"error","camelCase":"error","snakeCase":"error","pascalCase":"Error","screamingSnakeCase":"ERROR","wireValue":"error"},"errorContentKey":{"originalValue":"content","camelCase":"content","snakeCase":"content","pascalCase":"Content","screamingSnakeCase":"CONTENT","wireValue":"content"}},"errorsV2":{"errorInstanceIdKey":{"wireValue":"errorInstanceId","name":{"safeName":{"originalValue":"errorInstanceId","camelCase":"errorInstanceId","pascalCase":"ErrorInstanceId","snakeCase":"error_instance_id","screamingSnakeCase":"ERROR_INSTANCE_ID"},"unsafeName":{"originalValue":"errorInstanceId","camelCase":"errorInstanceId","pascalCase":"ErrorInstanceId","snakeCase":"error_instance_id","screamingSnakeCase":"ERROR_INSTANCE_ID"}}},"errorDiscriminant":{"name":{"unsafeName":{"originalValue":"error","camelCase":"error","snakeCase":"error","pascalCase":"Error","screamingSnakeCase":"ERROR"},"safeName":{"originalValue":"error","camelCase":"error","snakeCase":"error","pascalCase":"Error","screamingSnakeCase":"ERROR"}},"wireValue":"error"},"errorContentKey":{"name":{"unsafeName":{"originalValue":"content","camelCase":"content","snakeCase":"content","pascalCase":"Content","screamingSnakeCase":"CONTENT"},"safeName":{"originalValue":"content","camelCase":"content","snakeCase":"content","pascalCase":"Content","screamingSnakeCase":"CONTENT"}},"wireValue":"content"}}},"environments":[],"errorDiscriminant":{"safeName":{"originalValue":"error","camelCase":"error","pascalCase":"Error","snakeCase":"error","screamingSnakeCase":"ERROR"},"unsafeName":{"originalValue":"error","camelCase":"error","pascalCase":"Error","snakeCase":"error","screamingSnakeCase":"ERROR"}},"errorDiscriminationStrategy":{"discriminant":{"wireValue":"error","name":{"safeName":{"originalValue":"error","camelCase":"error","pascalCase":"Error","snakeCase":"error","screamingSnakeCase":"ERROR"},"unsafeName":{"originalValue":"error","camelCase":"error","pascalCase":"Error","snakeCase":"error","screamingSnakeCase":"ERROR"}}},"contentProperty":{"wireValue":"content","name":{"safeName":{"originalValue":"content","camelCase":"content","pascalCase":"Content","snakeCase":"content","screamingSnakeCase":"CONTENT"},"unsafeName":{"originalValue":"content","camelCase":"content","pascalCase":"Content","snakeCase":"content","screamingSnakeCase":"CONTENT"}}},"type":"property"},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version"}},"errors":[{"name":{"name":"BadRequestError","nameV2":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"nameV3":{"safeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"unsafeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"}},"fernFilepath":[{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}],"fernFilepathV2":[{"safeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"},"unsafeName":{"originalValue":"commons","camelCase":"commons","pascalCase":"Commons","snakeCase":"commons","screamingSnakeCase":"COMMONS"}}]},"discriminantValue":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR","wireValue":"BadRequestError"},"discriminantValueV2":{"wireValue":"BadRequestError","name":{"safeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"unsafeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"}}},"discriminantValueV3":{"wireValue":"content","name":{"safeName":{"originalValue":"content","camelCase":"content","pascalCase":"Content","snakeCase":"content","screamingSnakeCase":"CONTENT"},"unsafeName":{"originalValue":"content","camelCase":"content","pascalCase":"Content","snakeCase":"content","screamingSnakeCase":"CONTENT"}},"type":"property"},"discriminantValueV4":{"wireValue":"BadRequestError","name":{"safeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"},"unsafeName":{"originalValue":"BadRequestError","camelCase":"badRequestError","pascalCase":"BadRequestError","snakeCase":"bad_request_error","screamingSnakeCase":"BAD_REQUEST_ERROR"}}},"type":{"aliasOf":{"_type":"void"},"resolvedType":{"_type":"void"},"_type":"alias"},"http":{"statusCode":400},"statusCode":400},{"name":{"name":"NotFoundError","nameV2":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"nameV3":{"safeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"unsafeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"}},"fernFilepath":[{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}],"fernFilepathV2":[{"safeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"},"unsafeName":{"originalValue":"imdb","camelCase":"imdb","pascalCase":"Imdb","snakeCase":"imdb","screamingSnakeCase":"IMDB"}}]},"discriminantValue":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR","wireValue":"NotFoundError"},"discriminantValueV2":{"wireValue":"NotFoundError","name":{"safeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"unsafeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"}}},"discriminantValueV3":{"wireValue":"content","name":{"safeName":{"originalValue":"content","camelCase":"content","pascalCase":"Content","snakeCase":"content","screamingSnakeCase":"CONTENT"},"unsafeName":{"originalValue":"content","camelCase":"content","pascalCase":"Content","snakeCase":"content","screamingSnakeCase":"CONTENT"}},"type":"property"},"discriminantValueV4":{"wireValue":"NotFoundError","name":{"safeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"},"unsafeName":{"originalValue":"NotFoundError","camelCase":"notFoundError","pascalCase":"NotFoundError","snakeCase":"not_found_error","screamingSnakeCase":"NOT_FOUND_ERROR"}}},"type":{"aliasOf":{"primitive":"STRING","_type":"primitive"},"resolvedType":{"primitive":"STRING","_type":"primitive"},"_type":"alias"},"typeV2":{"aliasOf":{"primitive":"STRING","_type":"primitive"},"resolvedType":{"primitive":"STRING","_type":"primitive"},"_type":"alias"},"typeV3":{"primitive":"STRING","_type":"primitive"},"http":{"statusCode":404},"statusCode":404}]}"`; @@ -22,9 +22,9 @@ exports[`ir > {"name":"simple","audiences":["internal"]} 1`] = `"{"fdrApiDefinit exports[`ir > {"name":"simple","audiences":["test"]} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":"foo bar baz","auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[{"name":{"name":{"originalName":"apiVersion","camelCase":{"unsafeName":"apiVersion","safeName":"apiVersion"},"snakeCase":{"unsafeName":"api_version","safeName":"api_version"},"screamingSnakeCase":{"unsafeName":"API_VERSION","safeName":"API_VERSION"},"pascalCase":{"unsafeName":"ApiVersion","safeName":"ApiVersion"}},"wireValue":"X-API-VERSION"},"valueType":{"_type":"container","container":{"_type":"optional","optional":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}}},"env":null,"availability":null,"docs":null}],"idempotencyHeaders":[],"types":{"type_commons:Type":{"inline":false,"name":{"name":{"originalName":"Type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Type"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:MovieId":{"inline":false,"name":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id1"}}},"jsonExample":"id1"}},"jsonExample":"id1","docs":null},{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id2"}}},"jsonExample":"id2"}},"jsonExample":"id2","docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:CreateMovieRequest":{"inline":false,"name":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"valueType":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"Example1","camelCase":{"unsafeName":"example1","safeName":"example1"},"snakeCase":{"unsafeName":"example_1","safeName":"example_1"},"screamingSnakeCase":{"unsafeName":"EXAMPLE_1","safeName":"EXAMPLE_1"},"pascalCase":{"unsafeName":"Example1","safeName":"Example1"}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Winnie the Pooh"}}},"jsonExample":"Winnie the Pooh"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}}]},"jsonExample":{"title":"Winnie the Pooh","ratings":[1,2,3]},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null}},"errors":{"error_commons:BadRequestError":{"name":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"discriminantValue":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"wireValue":"BadRequestError"},"statusCode":400,"type":null,"examples":[],"docs":null}},"services":{"service_imdb":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_imdb.createMovie","name":{"originalName":"createMovie","camelCase":{"unsafeName":"createMovie","safeName":"createMovie"},"snakeCase":{"unsafeName":"create_movie","safeName":"create_movie"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE","safeName":"CREATE_MOVIE"},"pascalCase":{"unsafeName":"CreateMovie","safeName":"CreateMovie"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies"}]},"pathParameters":[],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"queryParameters":[],"headers":[],"requestBody":{"type":"reference","requestBodyType":{"_type":"named","name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest","default":null,"inline":null},"contentType":null,"docs":null},"sdkRequest":{"shape":{"type":"justRequestBody","value":{"type":"typeReference","requestBodyType":{"_type":"named","name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest","default":null,"inline":null},"contentType":null,"docs":null}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"json","value":{"type":"response","responseBodyType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"docs":null}},"status-code":null},"errors":[{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[{"example":{"id":"0ec9625807680a71388bf490199238a7c22569a5bf7092901fcb77bdd393e496","name":null,"url":"/test/root/movies","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[],"request":{"type":"reference","shape":{"type":"named","typeName":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Shrek"}}},"jsonExample":"Shrek"},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[10,10,10,10]},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}}]}},"jsonExample":{"title":"Shrek","ratings":[10,10,10,10]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"shrek-123"}}},"jsonExample":"shrek-123"}}},"jsonExample":"shrek-123"}}},"docs":null},"codeSamples":null},{"example":{"id":"624277beb517e6d1281e37e2781b35851d2d5de808ab821d6bad4c0917b014a5","name":null,"url":"/test/root/movies","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[],"request":{"type":"reference","shape":{"type":"named","typeName":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Winnie the Pooh"}}},"jsonExample":"Winnie the Pooh"},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}}]}},"jsonExample":{"title":"Winnie the Pooh","ratings":[1,2,3]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"shrek-123"}}},"jsonExample":"shrek-123"}}},"jsonExample":"shrek-123"}}},"docs":null},"codeSamples":null}],"autogeneratedExamples":[{"example":{"id":"3435f6402495ee7805cf225e15f28f07a6f16235","url":"/test/rootPathParam/movies","name":null,"endpointHeaders":[],"endpointPathParameters":[],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":{"type":"reference","shape":{"type":"named","shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"title"}}},"jsonExample":"title"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1},{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1.1,1.1]}}]},"typeName":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},"jsonExample":{"title":"title","ratings":[1.1,1.1]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"string"}}},"jsonExample":"string"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"string"}}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"property","discriminant":{"name":{"originalName":"error","camelCase":{"unsafeName":"error","safeName":"error"},"snakeCase":{"unsafeName":"error","safeName":"error"},"screamingSnakeCase":{"unsafeName":"ERROR","safeName":"ERROR"},"pascalCase":{"unsafeName":"Error","safeName":"Error"}},"wireValue":"error"},"contentProperty":{"name":{"originalName":"content","camelCase":{"unsafeName":"content","safeName":"content"},"snakeCase":{"unsafeName":"content","safeName":"content"},"screamingSnakeCase":{"unsafeName":"CONTENT","safeName":"CONTENT"},"pascalCase":{"unsafeName":"Content","safeName":"Content"}},"wireValue":"content"}},"basePath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":""}]},"pathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"variables":[],"serviceTypeReferenceInfo":{"sharedTypes":["type_commons:Type"],"typesReferencedOnlyByService":{"service_imdb":["type_imdb:MovieId","type_imdb:CreateMovieRequest"]}},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_commons":{"name":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"service":null,"types":["type_commons:Type"],"errors":["error_commons:BadRequestError"],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_imdb":{"name":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"service":"service_imdb","types":["type_imdb:MovieId","type_imdb:CreateMovieRequest"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_commons","subpackage_imdb"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; -exports[`ir > {"name":"simple"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":"foo bar baz","auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[{"name":{"name":{"originalName":"apiVersion","camelCase":{"unsafeName":"apiVersion","safeName":"apiVersion"},"snakeCase":{"unsafeName":"api_version","safeName":"api_version"},"screamingSnakeCase":{"unsafeName":"API_VERSION","safeName":"API_VERSION"},"pascalCase":{"unsafeName":"ApiVersion","safeName":"ApiVersion"}},"wireValue":"X-API-VERSION"},"valueType":{"_type":"container","container":{"_type":"optional","optional":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}}},"env":null,"availability":null,"docs":null}],"idempotencyHeaders":[],"types":{"type_commons:Internal":{"inline":false,"name":{"name":{"originalName":"Internal","camelCase":{"unsafeName":"internal","safeName":"internal"},"snakeCase":{"unsafeName":"internal","safeName":"internal"},"screamingSnakeCase":{"unsafeName":"INTERNAL","safeName":"INTERNAL"},"pascalCase":{"unsafeName":"Internal","safeName":"Internal"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Internal"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_commons:Type":{"inline":false,"name":{"name":{"originalName":"Type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Type"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_commons:UndiscriminatedUnion":{"inline":false,"name":{"name":{"originalName":"UndiscriminatedUnion","camelCase":{"unsafeName":"undiscriminatedUnion","safeName":"undiscriminatedUnion"},"snakeCase":{"unsafeName":"undiscriminated_union","safeName":"undiscriminated_union"},"screamingSnakeCase":{"unsafeName":"UNDISCRIMINATED_UNION","safeName":"UNDISCRIMINATED_UNION"},"pascalCase":{"unsafeName":"UndiscriminatedUnion","safeName":"UndiscriminatedUnion"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:UndiscriminatedUnion"},"shape":{"_type":"undiscriminatedUnion","members":[{"type":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"docs":null},{"type":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}}},"docs":null},{"type":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}},"docs":null},{"type":{"_type":"container","container":{"_type":"list","list":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}}}}}},"docs":null}]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:Director":{"inline":false,"name":{"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"valueType":{"_type":"named","name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Age","default":null,"inline":null},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":["type_director:Age"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"GeorgeExample","camelCase":{"unsafeName":"georgeExample","safeName":"georgeExample"},"snakeCase":{"unsafeName":"george_example","safeName":"george_example"},"screamingSnakeCase":{"unsafeName":"GEORGE_EXAMPLE","safeName":"GEORGE_EXAMPLE"},"pascalCase":{"unsafeName":"GeorgeExample","safeName":"GeorgeExample"}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"George the Director"}}},"jsonExample":"George the Director"},"originalTypeDeclaration":{"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"}},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Age","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":20}},"jsonExample":20}}},"jsonExample":20},"originalTypeDeclaration":{"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"}}]},"jsonExample":{"name":"George the Director","age":20},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:Age":{"inline":false,"name":{"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Age"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"Example1","camelCase":{"unsafeName":"example1","safeName":"example1"},"snakeCase":{"unsafeName":"example_1","safeName":"example_1"},"screamingSnakeCase":{"unsafeName":"EXAMPLE_1","safeName":"EXAMPLE_1"},"pascalCase":{"unsafeName":"Example1","safeName":"Example1"}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":20}},"jsonExample":20}},"jsonExample":20,"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:LiteralBoolean":{"inline":false,"name":{"name":{"originalName":"LiteralBoolean","camelCase":{"unsafeName":"literalBoolean","safeName":"literalBoolean"},"snakeCase":{"unsafeName":"literal_boolean","safeName":"literal_boolean"},"screamingSnakeCase":{"unsafeName":"LITERAL_BOOLEAN","safeName":"LITERAL_BOOLEAN"},"pascalCase":{"unsafeName":"LiteralBoolean","safeName":"LiteralBoolean"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:LiteralBoolean"},"shape":{"_type":"alias","aliasOf":{"_type":"container","container":{"_type":"literal","literal":{"type":"boolean","boolean":true}}},"resolvedType":{"_type":"container","container":{"_type":"literal","literal":{"type":"boolean","boolean":true}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:LiteralString":{"inline":false,"name":{"name":{"originalName":"LiteralString","camelCase":{"unsafeName":"literalString","safeName":"literalString"},"snakeCase":{"unsafeName":"literal_string","safeName":"literal_string"},"screamingSnakeCase":{"unsafeName":"LITERAL_STRING","safeName":"LITERAL_STRING"},"pascalCase":{"unsafeName":"LiteralString","safeName":"LiteralString"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:LiteralString"},"shape":{"_type":"alias","aliasOf":{"_type":"container","container":{"_type":"literal","literal":{"type":"string","string":"hello"}}},"resolvedType":{"_type":"container","container":{"_type":"literal","literal":{"type":"string","string":"hello"}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:CurrencyAmount":{"inline":false,"name":{"name":{"originalName":"CurrencyAmount","camelCase":{"unsafeName":"currencyAmount","safeName":"currencyAmount"},"snakeCase":{"unsafeName":"currency_amount","safeName":"currency_amount"},"screamingSnakeCase":{"unsafeName":"CURRENCY_AMOUNT","safeName":"CURRENCY_AMOUNT"},"pascalCase":{"unsafeName":"CurrencyAmount","safeName":"CurrencyAmount"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CurrencyAmount"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"$4.50"}}},"jsonExample":"$4.50"}},"jsonExample":"$4.50","docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:MovieId":{"inline":false,"name":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id1"}}},"jsonExample":"id1"}},"jsonExample":"id1","docs":null},{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id2"}}},"jsonExample":"id2"}},"jsonExample":"id2","docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:ActorId":{"inline":false,"name":{"name":{"originalName":"ActorId","camelCase":{"unsafeName":"actorId","safeName":"actorId"},"snakeCase":{"unsafeName":"actor_id","safeName":"actor_id"},"screamingSnakeCase":{"unsafeName":"ACTOR_ID","safeName":"ACTOR_ID"},"pascalCase":{"unsafeName":"ActorId","safeName":"ActorId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:ActorId"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:Movie":{"inline":false,"name":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"availability":null,"docs":null},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"valueType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":["type_imdb:MovieId"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"my-movie-id"}}},"jsonExample":"my-movie-id"}}},"jsonExample":"my-movie-id"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Goodwill Hunting"}}},"jsonExample":"Goodwill Hunting"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"value":{"shape":{"type":"primitive","primitive":{"type":"double","double":14.5}},"jsonExample":14.5},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}}]},"jsonExample":{"id":"my-movie-id","title":"Goodwill Hunting","rating":14.5},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:CreateMovieRequest":{"inline":false,"name":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"valueType":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"Example1","camelCase":{"unsafeName":"example1","safeName":"example1"},"snakeCase":{"unsafeName":"example_1","safeName":"example_1"},"screamingSnakeCase":{"unsafeName":"EXAMPLE_1","safeName":"EXAMPLE_1"},"pascalCase":{"unsafeName":"Example1","safeName":"Example1"}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Winnie the Pooh"}}},"jsonExample":"Winnie the Pooh"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}}]},"jsonExample":{"title":"Winnie the Pooh","ratings":[1,2,3]},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:DirectorWrapper":{"inline":false,"name":{"name":{"originalName":"DirectorWrapper","camelCase":{"unsafeName":"directorWrapper","safeName":"directorWrapper"},"snakeCase":{"unsafeName":"director_wrapper","safeName":"director_wrapper"},"screamingSnakeCase":{"unsafeName":"DIRECTOR_WRAPPER","safeName":"DIRECTOR_WRAPPER"},"pascalCase":{"unsafeName":"DirectorWrapper","safeName":"DirectorWrapper"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:DirectorWrapper"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"valueType":{"_type":"named","name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director","default":null,"inline":null},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":["type_director:Director","type_director:Age"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"George the Director"}}},"jsonExample":"George the Director"},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Age","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":20}},"jsonExample":20}}},"jsonExample":20},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}}]}},"jsonExample":{"name":"George the Director","age":20}},"originalTypeDeclaration":{"name":{"originalName":"DirectorWrapper","camelCase":{"unsafeName":"directorWrapper","safeName":"directorWrapper"},"snakeCase":{"unsafeName":"director_wrapper","safeName":"director_wrapper"},"screamingSnakeCase":{"unsafeName":"DIRECTOR_WRAPPER","safeName":"DIRECTOR_WRAPPER"},"pascalCase":{"unsafeName":"DirectorWrapper","safeName":"DirectorWrapper"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:DirectorWrapper"}}]},"jsonExample":{"director":{"name":"George the Director","age":20}},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:EmptyObject":{"inline":false,"name":{"name":{"originalName":"EmptyObject","camelCase":{"unsafeName":"emptyObject","safeName":"emptyObject"},"snakeCase":{"unsafeName":"empty_object","safeName":"empty_object"},"screamingSnakeCase":{"unsafeName":"EMPTY_OBJECT","safeName":"EMPTY_OBJECT"},"pascalCase":{"unsafeName":"EmptyObject","safeName":"EmptyObject"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:EmptyObject"},"shape":{"_type":"object","extends":[],"properties":[],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:Person":{"inline":false,"name":{"name":{"originalName":"Person","camelCase":{"unsafeName":"person","safeName":"person"},"snakeCase":{"unsafeName":"person","safeName":"person"},"screamingSnakeCase":{"unsafeName":"PERSON","safeName":"PERSON"},"pascalCase":{"unsafeName":"Person","safeName":"Person"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Person"},"shape":{"_type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"extends":[],"baseProperties":[],"types":[{"discriminantValue":{"name":{"originalName":"actor","camelCase":{"unsafeName":"actor","safeName":"actor"},"snakeCase":{"unsafeName":"actor","safeName":"actor"},"screamingSnakeCase":{"unsafeName":"ACTOR","safeName":"ACTOR"},"pascalCase":{"unsafeName":"Actor","safeName":"Actor"}},"wireValue":"actor"},"shape":{"_type":"singleProperty","name":{"name":{"originalName":"value","camelCase":{"unsafeName":"value","safeName":"value"},"snakeCase":{"unsafeName":"value","safeName":"value"},"screamingSnakeCase":{"unsafeName":"VALUE","safeName":"VALUE"},"pascalCase":{"unsafeName":"Value","safeName":"Value"}},"wireValue":"value"},"type":{"_type":"named","name":{"originalName":"ActorId","camelCase":{"unsafeName":"actorId","safeName":"actorId"},"snakeCase":{"unsafeName":"actor_id","safeName":"actor_id"},"screamingSnakeCase":{"unsafeName":"ACTOR_ID","safeName":"ACTOR_ID"},"pascalCase":{"unsafeName":"ActorId","safeName":"ActorId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:ActorId","default":null,"inline":null}},"displayName":null,"availability":null,"docs":null},{"discriminantValue":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"shape":{"_type":"samePropertiesAsObject","name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"},"displayName":null,"availability":null,"docs":null},{"discriminantValue":{"name":{"originalName":"producer","camelCase":{"unsafeName":"producer","safeName":"producer"},"snakeCase":{"unsafeName":"producer","safeName":"producer"},"screamingSnakeCase":{"unsafeName":"PRODUCER","safeName":"PRODUCER"},"pascalCase":{"unsafeName":"Producer","safeName":"Producer"}},"wireValue":"producer"},"shape":{"_type":"samePropertiesAsObject","name":{"originalName":"EmptyObject","camelCase":{"unsafeName":"emptyObject","safeName":"emptyObject"},"snakeCase":{"unsafeName":"empty_object","safeName":"empty_object"},"screamingSnakeCase":{"unsafeName":"EMPTY_OBJECT","safeName":"EMPTY_OBJECT"},"pascalCase":{"unsafeName":"EmptyObject","safeName":"EmptyObject"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:EmptyObject"},"displayName":null,"availability":null,"docs":null},{"discriminantValue":{"name":{"originalName":"cinematographer","camelCase":{"unsafeName":"cinematographer","safeName":"cinematographer"},"snakeCase":{"unsafeName":"cinematographer","safeName":"cinematographer"},"screamingSnakeCase":{"unsafeName":"CINEMATOGRAPHER","safeName":"CINEMATOGRAPHER"},"pascalCase":{"unsafeName":"Cinematographer","safeName":"Cinematographer"}},"wireValue":"cinematographer"},"shape":{"_type":"samePropertiesAsObject","name":{"originalName":"EmptyObject","camelCase":{"unsafeName":"emptyObject","safeName":"emptyObject"},"snakeCase":{"unsafeName":"empty_object","safeName":"empty_object"},"screamingSnakeCase":{"unsafeName":"EMPTY_OBJECT","safeName":"EMPTY_OBJECT"},"pascalCase":{"unsafeName":"EmptyObject","safeName":"EmptyObject"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:EmptyObject"},"displayName":null,"availability":null,"docs":"i am docs"}]},"referencedTypes":["type_imdb:ActorId","type_director:Director","type_director:Age","type_imdb:EmptyObject"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"PersonExample1","camelCase":{"unsafeName":"personExample1","safeName":"personExample1"},"snakeCase":{"unsafeName":"person_example_1","safeName":"person_example_1"},"screamingSnakeCase":{"unsafeName":"PERSON_EXAMPLE_1","safeName":"PERSON_EXAMPLE_1"},"pascalCase":{"unsafeName":"PersonExample1","safeName":"PersonExample1"}},"shape":{"type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"singleUnionType":{"wireDiscriminantValue":{"name":{"originalName":"actor","camelCase":{"unsafeName":"actor","safeName":"actor"},"snakeCase":{"unsafeName":"actor","safeName":"actor"},"screamingSnakeCase":{"unsafeName":"ACTOR","safeName":"ACTOR"},"pascalCase":{"unsafeName":"Actor","safeName":"Actor"}},"wireValue":"actor"},"shape":{"type":"singleProperty","shape":{"type":"named","typeName":{"typeId":"type_imdb:ActorId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"ActorId","camelCase":{"unsafeName":"actorId","safeName":"actorId"},"snakeCase":{"unsafeName":"actor_id","safeName":"actor_id"},"screamingSnakeCase":{"unsafeName":"ACTOR_ID","safeName":"ACTOR_ID"},"pascalCase":{"unsafeName":"ActorId","safeName":"ActorId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Matt Damon"}}},"jsonExample":"Matt Damon"}}},"jsonExample":"Matt Damon"}}},"jsonExample":{"type":"actor","value":"Matt Damon"},"docs":"this is a person example"},{"name":null,"shape":{"type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"singleUnionType":{"wireDiscriminantValue":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"shape":{"type":"samePropertiesAsObject","typeId":"type_director:Director","object":{"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"George the Directory"}}},"jsonExample":"George the Directory"},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Age","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":100}},"jsonExample":100}}},"jsonExample":100},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}}]}}}},"jsonExample":{"type":"director","name":"George the Directory","age":100},"docs":null},{"name":null,"shape":{"type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"singleUnionType":{"wireDiscriminantValue":{"name":{"originalName":"producer","camelCase":{"unsafeName":"producer","safeName":"producer"},"snakeCase":{"unsafeName":"producer","safeName":"producer"},"screamingSnakeCase":{"unsafeName":"PRODUCER","safeName":"PRODUCER"},"pascalCase":{"unsafeName":"Producer","safeName":"Producer"}},"wireValue":"producer"},"shape":{"type":"samePropertiesAsObject","typeId":"type_imdb:EmptyObject","object":{"properties":[]}}}},"jsonExample":{"type":"producer"},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:UnknownRequest":{"inline":false,"name":{"name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"unknown","camelCase":{"unsafeName":"unknown","safeName":"unknown"},"snakeCase":{"unsafeName":"unknown","safeName":"unknown"},"screamingSnakeCase":{"unsafeName":"UNKNOWN","safeName":"UNKNOWN"},"pascalCase":{"unsafeName":"Unknown","safeName":"Unknown"}},"wireValue":"unknown"},"valueType":{"_type":"unknown"},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:RecursiveType":{"inline":false,"name":{"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType"},"shape":{"_type":"object","extends":[{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}],"properties":[{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"valueType":{"_type":"container","container":{"_type":"list","list":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"valueType":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"availability":null,"docs":null}]},"referencedTypes":["type_imdb:CreateMovieRequest","type_imdb:RecursiveType"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"The Godfather"}}},"jsonExample":"The Godfather"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":5}},"jsonExample":5},{"shape":{"type":"primitive","primitive":{"type":"double","double":9}},"jsonExample":9}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[10,5,9]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"named","typeName":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"The Godfather II"}}},"jsonExample":"The Godfather II"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":11}},"jsonExample":11}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[10,11]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[]},"originalTypeDeclaration":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}}}]}},"jsonExample":{"title":"The Godfather II","ratings":[10,11],"selfReferencing":[]}},{"shape":{"type":"named","typeName":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"The Godfather III"}}},"jsonExample":"The Godfather III"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[]},"originalTypeDeclaration":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}}}]}},"jsonExample":{"title":"The Godfather III","ratings":[],"selfReferencing":[]}}],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[{"title":"The Godfather II","ratings":[10,11],"selfReferencing":[]},{"title":"The Godfather III","ratings":[],"selfReferencing":[]}]},"originalTypeDeclaration":{"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType"}}]},"jsonExample":{"title":"The Godfather","ratings":[10,5,9],"selfReferencing":[{"title":"The Godfather II","ratings":[10,11],"selfReferencing":[]},{"title":"The Godfather III","ratings":[],"selfReferencing":[]}]},"docs":null},{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Goodfellas"}}},"jsonExample":"Goodfellas"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[]},"originalTypeDeclaration":{"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType"}}]},"jsonExample":{"title":"Goodfellas","ratings":[1,2,3],"selfReferencing":[]},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null}},"errors":{"error_commons:BadRequestError":{"name":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"discriminantValue":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"wireValue":"BadRequestError"},"statusCode":400,"type":null,"examples":[],"docs":null},"error_imdb:NotFoundError":{"name":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"errorId":"error_imdb:NotFoundError"},"discriminantValue":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"wireValue":"NotFoundError"},"statusCode":404,"type":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"examples":[],"docs":null}},"services":{"service_imdb":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_imdb.internalEndpoint","name":{"originalName":"internalEndpoint","camelCase":{"unsafeName":"internalEndpoint","safeName":"internalEndpoint"},"snakeCase":{"unsafeName":"internal_endpoint","safeName":"internal_endpoint"},"screamingSnakeCase":{"unsafeName":"INTERNAL_ENDPOINT","safeName":"INTERNAL_ENDPOINT"},"pascalCase":{"unsafeName":"InternalEndpoint","safeName":"InternalEndpoint"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies"}]},"pathParameters":[],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"queryParameters":[],"headers":[],"requestBody":{"type":"reference","requestBodyType":{"_type":"named","name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest","default":null,"inline":null},"contentType":null,"docs":null},"sdkRequest":{"shape":{"type":"justRequestBody","value":{"type":"typeReference","requestBodyType":{"_type":"named","name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest","default":null,"inline":null},"contentType":null,"docs":null}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"json","value":{"type":"response","responseBodyType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"docs":null}},"status-code":null},"errors":[{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[],"autogeneratedExamples":[{"example":{"id":"6ddea0588fd84ef0a9732ab58c4e1d50078da707","url":"/test/rootPathParam/movies","name":null,"endpointHeaders":[],"endpointPathParameters":[],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":{"type":"reference","shape":{"type":"named","shape":{"type":"object","properties":[{"name":{"name":{"originalName":"unknown","camelCase":{"unsafeName":"unknown","safeName":"unknown"},"snakeCase":{"unsafeName":"unknown","safeName":"unknown"},"screamingSnakeCase":{"unsafeName":"UNKNOWN","safeName":"UNKNOWN"},"pascalCase":{"unsafeName":"Unknown","safeName":"Unknown"}},"wireValue":"unknown"},"originalTypeDeclaration":{"name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest"},"value":{"shape":{"type":"unknown","unknown":{"key":"value"}},"jsonExample":{"key":"value"}}}]},"typeName":{"name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest"}},"jsonExample":{"unknown":{"key":"value"}}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"string"}}},"jsonExample":"string"}}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_imdb.createMovie","name":{"originalName":"createMovie","camelCase":{"unsafeName":"createMovie","safeName":"createMovie"},"snakeCase":{"unsafeName":"create_movie","safeName":"create_movie"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE","safeName":"CREATE_MOVIE"},"pascalCase":{"unsafeName":"CreateMovie","safeName":"CreateMovie"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies"}]},"pathParameters":[],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"queryParameters":[],"headers":[],"requestBody":{"type":"reference","requestBodyType":{"_type":"named","name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest","default":null,"inline":null},"contentType":null,"docs":null},"sdkRequest":{"shape":{"type":"justRequestBody","value":{"type":"typeReference","requestBodyType":{"_type":"named","name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest","default":null,"inline":null},"contentType":null,"docs":null}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"json","value":{"type":"response","responseBodyType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"docs":null}},"status-code":null},"errors":[{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[{"example":{"id":"0ec9625807680a71388bf490199238a7c22569a5bf7092901fcb77bdd393e496","name":null,"url":"/test/root/movies","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[],"request":{"type":"reference","shape":{"type":"named","typeName":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Shrek"}}},"jsonExample":"Shrek"},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[10,10,10,10]},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}}]}},"jsonExample":{"title":"Shrek","ratings":[10,10,10,10]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"shrek-123"}}},"jsonExample":"shrek-123"}}},"jsonExample":"shrek-123"}}},"docs":null},"codeSamples":null},{"example":{"id":"624277beb517e6d1281e37e2781b35851d2d5de808ab821d6bad4c0917b014a5","name":null,"url":"/test/root/movies","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[],"request":{"type":"reference","shape":{"type":"named","typeName":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Winnie the Pooh"}}},"jsonExample":"Winnie the Pooh"},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}}]}},"jsonExample":{"title":"Winnie the Pooh","ratings":[1,2,3]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"shrek-123"}}},"jsonExample":"shrek-123"}}},"jsonExample":"shrek-123"}}},"docs":null},"codeSamples":null}],"autogeneratedExamples":[{"example":{"id":"3435f6402495ee7805cf225e15f28f07a6f16235","url":"/test/rootPathParam/movies","name":null,"endpointHeaders":[],"endpointPathParameters":[],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":{"type":"reference","shape":{"type":"named","shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"title"}}},"jsonExample":"title"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1},{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1.1,1.1]}}]},"typeName":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},"jsonExample":{"title":"title","ratings":[1.1,1.1]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"string"}}},"jsonExample":"string"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"string"}}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_imdb.getMovie","name":{"originalName":"getMovie","camelCase":{"unsafeName":"getMovie","safeName":"getMovie"},"snakeCase":{"unsafeName":"get_movie","safeName":"get_movie"},"screamingSnakeCase":{"unsafeName":"GET_MOVIE","safeName":"GET_MOVIE"},"pascalCase":{"unsafeName":"GetMovie","safeName":"GetMovie"}},"displayName":"Get Movie by Id","auth":false,"idempotent":false,"baseUrl":null,"method":"GET","basePath":null,"path":{"head":"/","parts":[{"pathParameter":"movieId","tail":""}]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies/"},{"pathParameter":"movieId","tail":""}]},"pathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null},{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"allowMultiple":true,"availability":null,"docs":null}],"headers":[],"requestBody":null,"sdkRequest":{"shape":{"type":"wrapper","wrapperName":{"originalName":"GetMovieRequest","camelCase":{"unsafeName":"getMovieRequest","safeName":"getMovieRequest"},"snakeCase":{"unsafeName":"get_movie_request","safeName":"get_movie_request"},"screamingSnakeCase":{"unsafeName":"GET_MOVIE_REQUEST","safeName":"GET_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"GetMovieRequest","safeName":"GetMovieRequest"}},"bodyKey":{"originalName":"body","camelCase":{"unsafeName":"body","safeName":"body"},"snakeCase":{"unsafeName":"body","safeName":"body"},"screamingSnakeCase":{"unsafeName":"BODY","safeName":"BODY"},"pascalCase":{"unsafeName":"Body","safeName":"Body"}}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"json","value":{"type":"response","responseBodyType":{"_type":"named","name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie","default":null,"inline":null},"docs":null}},"status-code":null},"errors":[{"error":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"errorId":"error_imdb:NotFoundError"},"docs":null},{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[{"example":{"id":"b631e6a0d36395e53918c8b8aa048cf1bfe8180757f41402426ec3c109737ddd","name":null,"url":"/test/root/movies/id-123","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}}},"jsonExample":"id-123"}}],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"hello"}}},"jsonExample":"hello"},"shape":{"type":"exploded"}}],"request":null,"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}}},"jsonExample":"id-123"},"originalTypeDeclaration":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}}},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Shrek"}}},"jsonExample":"Shrek"},"originalTypeDeclaration":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}}},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"value":{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},"originalTypeDeclaration":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}}}]}},"jsonExample":{"id":"id-123","title":"Shrek","rating":10}}}},"docs":null},"codeSamples":null},{"example":{"id":"e0f04d26bcd1b030581f6d67cbd9d4be232fc0bc43bad08ffb1adb038ecbce8f","name":null,"url":"/test/root/movies/id-123","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}}},"jsonExample":"id-123"}}],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"hello"}}},"jsonExample":"hello"},"shape":{"type":"exploded"}}],"request":null,"response":{"type":"error","error":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"errorId":"error_imdb:NotFoundError"},"body":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}},"docs":null},"codeSamples":null}],"autogeneratedExamples":[{"example":{"id":"3285e2d8c07bd6f2c3100d3de09fb7039f7d4df8","url":"/test/rootPathParam/movies/movieId","name":null,"endpointHeaders":[],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"movieId"}}},"jsonExample":"movieId"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"movieId"}}],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"shape":{"type":"exploded"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"movieName"}}},"jsonExample":"movieName"}}],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":null,"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","shape":{"type":"object","properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id"}}},"jsonExample":"id"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"id"}},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"title"}}},"jsonExample":"title"}},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"value":{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1}}]},"typeName":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}},"jsonExample":{"id":"id","title":"title","rating":1.1}}}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_imdb.delete","name":{"originalName":"delete","camelCase":{"unsafeName":"delete","safeName":"delete"},"snakeCase":{"unsafeName":"delete","safeName":"delete"},"screamingSnakeCase":{"unsafeName":"DELETE","safeName":"DELETE"},"pascalCase":{"unsafeName":"Delete","safeName":"Delete"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"DELETE","basePath":null,"path":{"head":"/","parts":[{"pathParameter":"movieId","tail":""}]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies/"},{"pathParameter":"movieId","tail":""}]},"pathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null},{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"queryParameters":[],"headers":[],"requestBody":null,"sdkRequest":null,"response":{"body":null,"status-code":null},"errors":[{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[],"autogeneratedExamples":[{"example":{"id":"b45fd34c7d591a65682197d8d7b5b726c7e5a1f8","url":"/test/rootPathParam/movies/movieId","name":null,"endpointHeaders":[],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"movieId"}}},"jsonExample":"movieId"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"movieId"}}],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":null,"response":{"type":"ok","value":{"type":"body","value":null}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"property","discriminant":{"name":{"originalName":"error","camelCase":{"unsafeName":"error","safeName":"error"},"snakeCase":{"unsafeName":"error","safeName":"error"},"screamingSnakeCase":{"unsafeName":"ERROR","safeName":"ERROR"},"pascalCase":{"unsafeName":"Error","safeName":"Error"}},"wireValue":"error"},"contentProperty":{"name":{"originalName":"content","camelCase":{"unsafeName":"content","safeName":"content"},"snakeCase":{"unsafeName":"content","safeName":"content"},"screamingSnakeCase":{"unsafeName":"CONTENT","safeName":"CONTENT"},"pascalCase":{"unsafeName":"Content","safeName":"Content"}},"wireValue":"content"}},"basePath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":""}]},"pathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{"service_imdb":["type_imdb:MovieId","type_imdb:Movie","type_imdb:CreateMovieRequest","type_imdb:UnknownRequest"]},"sharedTypes":["type_commons:Internal","type_commons:Type","type_commons:UndiscriminatedUnion","type_director:Director","type_director:Age","type_director:LiteralBoolean","type_director:LiteralString","type_imdb:CurrencyAmount","type_imdb:ActorId","type_imdb:DirectorWrapper","type_imdb:EmptyObject","type_imdb:Person","type_imdb:RecursiveType"]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_commons":{"name":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"service":null,"types":["type_commons:Internal","type_commons:Type","type_commons:UndiscriminatedUnion"],"errors":["error_commons:BadRequestError"],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_director":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"service":null,"types":["type_director:Director","type_director:Age","type_director:LiteralBoolean","type_director:LiteralString"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_imdb":{"name":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"service":"service_imdb","types":["type_imdb:CurrencyAmount","type_imdb:MovieId","type_imdb:ActorId","type_imdb:Movie","type_imdb:CreateMovieRequest","type_imdb:DirectorWrapper","type_imdb:EmptyObject","type_imdb:Person","type_imdb:UnknownRequest","type_imdb:RecursiveType"],"errors":["error_imdb:NotFoundError"],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_commons","subpackage_director","subpackage_imdb"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; +exports[`ir > {"name":"simple"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":"foo bar baz","auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[{"name":{"name":{"originalName":"apiVersion","camelCase":{"unsafeName":"apiVersion","safeName":"apiVersion"},"snakeCase":{"unsafeName":"api_version","safeName":"api_version"},"screamingSnakeCase":{"unsafeName":"API_VERSION","safeName":"API_VERSION"},"pascalCase":{"unsafeName":"ApiVersion","safeName":"ApiVersion"}},"wireValue":"X-API-VERSION"},"valueType":{"_type":"container","container":{"_type":"optional","optional":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}}},"env":null,"availability":null,"docs":null}],"idempotencyHeaders":[],"types":{"type_commons:Internal":{"inline":false,"name":{"name":{"originalName":"Internal","camelCase":{"unsafeName":"internal","safeName":"internal"},"snakeCase":{"unsafeName":"internal","safeName":"internal"},"screamingSnakeCase":{"unsafeName":"INTERNAL","safeName":"INTERNAL"},"pascalCase":{"unsafeName":"Internal","safeName":"Internal"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Internal"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_commons:Type":{"inline":false,"name":{"name":{"originalName":"Type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:Type"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_commons:UndiscriminatedUnion":{"inline":false,"name":{"name":{"originalName":"UndiscriminatedUnion","camelCase":{"unsafeName":"undiscriminatedUnion","safeName":"undiscriminatedUnion"},"snakeCase":{"unsafeName":"undiscriminated_union","safeName":"undiscriminated_union"},"screamingSnakeCase":{"unsafeName":"UNDISCRIMINATED_UNION","safeName":"UNDISCRIMINATED_UNION"},"pascalCase":{"unsafeName":"UndiscriminatedUnion","safeName":"UndiscriminatedUnion"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:UndiscriminatedUnion"},"shape":{"_type":"undiscriminatedUnion","members":[{"type":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"docs":null},{"type":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}}},"docs":null},{"type":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}},"docs":null},{"type":{"_type":"container","container":{"_type":"list","list":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}}}}}},"docs":null}]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:Director":{"inline":false,"name":{"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"valueType":{"_type":"named","name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Age","default":null,"inline":null},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":["type_director:Age"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"GeorgeExample","camelCase":{"unsafeName":"georgeExample","safeName":"georgeExample"},"snakeCase":{"unsafeName":"george_example","safeName":"george_example"},"screamingSnakeCase":{"unsafeName":"GEORGE_EXAMPLE","safeName":"GEORGE_EXAMPLE"},"pascalCase":{"unsafeName":"GeorgeExample","safeName":"GeorgeExample"}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"George the Director"}}},"jsonExample":"George the Director"},"originalTypeDeclaration":{"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"}},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Age","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":20}},"jsonExample":20}}},"jsonExample":20},"originalTypeDeclaration":{"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"}}]},"jsonExample":{"name":"George the Director","age":20},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:Age":{"inline":false,"name":{"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Age"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"INTEGER","v2":{"type":"integer","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"Example1","camelCase":{"unsafeName":"example1","safeName":"example1"},"snakeCase":{"unsafeName":"example_1","safeName":"example_1"},"screamingSnakeCase":{"unsafeName":"EXAMPLE_1","safeName":"EXAMPLE_1"},"pascalCase":{"unsafeName":"Example1","safeName":"Example1"}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":20}},"jsonExample":20}},"jsonExample":20,"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:LiteralBoolean":{"inline":false,"name":{"name":{"originalName":"LiteralBoolean","camelCase":{"unsafeName":"literalBoolean","safeName":"literalBoolean"},"snakeCase":{"unsafeName":"literal_boolean","safeName":"literal_boolean"},"screamingSnakeCase":{"unsafeName":"LITERAL_BOOLEAN","safeName":"LITERAL_BOOLEAN"},"pascalCase":{"unsafeName":"LiteralBoolean","safeName":"LiteralBoolean"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:LiteralBoolean"},"shape":{"_type":"alias","aliasOf":{"_type":"container","container":{"_type":"literal","literal":{"type":"boolean","boolean":true}}},"resolvedType":{"_type":"container","container":{"_type":"literal","literal":{"type":"boolean","boolean":true}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_director:LiteralString":{"inline":false,"name":{"name":{"originalName":"LiteralString","camelCase":{"unsafeName":"literalString","safeName":"literalString"},"snakeCase":{"unsafeName":"literal_string","safeName":"literal_string"},"screamingSnakeCase":{"unsafeName":"LITERAL_STRING","safeName":"LITERAL_STRING"},"pascalCase":{"unsafeName":"LiteralString","safeName":"LiteralString"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:LiteralString"},"shape":{"_type":"alias","aliasOf":{"_type":"container","container":{"_type":"literal","literal":{"type":"string","string":"hello"}}},"resolvedType":{"_type":"container","container":{"_type":"literal","literal":{"type":"string","string":"hello"}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:CurrencyAmount":{"inline":false,"name":{"name":{"originalName":"CurrencyAmount","camelCase":{"unsafeName":"currencyAmount","safeName":"currencyAmount"},"snakeCase":{"unsafeName":"currency_amount","safeName":"currency_amount"},"screamingSnakeCase":{"unsafeName":"CURRENCY_AMOUNT","safeName":"CURRENCY_AMOUNT"},"pascalCase":{"unsafeName":"CurrencyAmount","safeName":"CurrencyAmount"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CurrencyAmount"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"$4.50"}}},"jsonExample":"$4.50"}},"jsonExample":"$4.50","docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:MovieId":{"inline":false,"name":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id1"}}},"jsonExample":"id1"}},"jsonExample":"id1","docs":null},{"name":null,"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id2"}}},"jsonExample":"id2"}},"jsonExample":"id2","docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:ActorId":{"inline":false,"name":{"name":{"originalName":"ActorId","camelCase":{"unsafeName":"actorId","safeName":"actorId"},"snakeCase":{"unsafeName":"actor_id","safeName":"actor_id"},"screamingSnakeCase":{"unsafeName":"ACTOR_ID","safeName":"ACTOR_ID"},"pascalCase":{"unsafeName":"ActorId","safeName":"ActorId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:ActorId"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:Movie":{"inline":false,"name":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"availability":null,"docs":null},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"valueType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":["type_imdb:MovieId"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"my-movie-id"}}},"jsonExample":"my-movie-id"}}},"jsonExample":"my-movie-id"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Goodwill Hunting"}}},"jsonExample":"Goodwill Hunting"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"value":{"shape":{"type":"primitive","primitive":{"type":"double","double":14.5}},"jsonExample":14.5},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}}]},"jsonExample":{"id":"my-movie-id","title":"Goodwill Hunting","rating":14.5},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:CreateMovieRequest":{"inline":false,"name":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"valueType":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"Example1","camelCase":{"unsafeName":"example1","safeName":"example1"},"snakeCase":{"unsafeName":"example_1","safeName":"example_1"},"screamingSnakeCase":{"unsafeName":"EXAMPLE_1","safeName":"EXAMPLE_1"},"pascalCase":{"unsafeName":"Example1","safeName":"Example1"}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Winnie the Pooh"}}},"jsonExample":"Winnie the Pooh"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}}]},"jsonExample":{"title":"Winnie the Pooh","ratings":[1,2,3]},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:DirectorWrapper":{"inline":false,"name":{"name":{"originalName":"DirectorWrapper","camelCase":{"unsafeName":"directorWrapper","safeName":"directorWrapper"},"snakeCase":{"unsafeName":"director_wrapper","safeName":"director_wrapper"},"screamingSnakeCase":{"unsafeName":"DIRECTOR_WRAPPER","safeName":"DIRECTOR_WRAPPER"},"pascalCase":{"unsafeName":"DirectorWrapper","safeName":"DirectorWrapper"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:DirectorWrapper"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"valueType":{"_type":"named","name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director","default":null,"inline":null},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":["type_director:Director","type_director:Age"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"George the Director"}}},"jsonExample":"George the Director"},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Age","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":20}},"jsonExample":20}}},"jsonExample":20},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}}]}},"jsonExample":{"name":"George the Director","age":20}},"originalTypeDeclaration":{"name":{"originalName":"DirectorWrapper","camelCase":{"unsafeName":"directorWrapper","safeName":"directorWrapper"},"snakeCase":{"unsafeName":"director_wrapper","safeName":"director_wrapper"},"screamingSnakeCase":{"unsafeName":"DIRECTOR_WRAPPER","safeName":"DIRECTOR_WRAPPER"},"pascalCase":{"unsafeName":"DirectorWrapper","safeName":"DirectorWrapper"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:DirectorWrapper"}}]},"jsonExample":{"director":{"name":"George the Director","age":20}},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:EmptyObject":{"inline":false,"name":{"name":{"originalName":"EmptyObject","camelCase":{"unsafeName":"emptyObject","safeName":"emptyObject"},"snakeCase":{"unsafeName":"empty_object","safeName":"empty_object"},"screamingSnakeCase":{"unsafeName":"EMPTY_OBJECT","safeName":"EMPTY_OBJECT"},"pascalCase":{"unsafeName":"EmptyObject","safeName":"EmptyObject"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:EmptyObject"},"shape":{"_type":"object","extends":[],"properties":[],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:Person":{"inline":false,"name":{"name":{"originalName":"Person","camelCase":{"unsafeName":"person","safeName":"person"},"snakeCase":{"unsafeName":"person","safeName":"person"},"screamingSnakeCase":{"unsafeName":"PERSON","safeName":"PERSON"},"pascalCase":{"unsafeName":"Person","safeName":"Person"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Person"},"shape":{"_type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"extends":[],"baseProperties":[],"types":[{"discriminantValue":{"name":{"originalName":"actor","camelCase":{"unsafeName":"actor","safeName":"actor"},"snakeCase":{"unsafeName":"actor","safeName":"actor"},"screamingSnakeCase":{"unsafeName":"ACTOR","safeName":"ACTOR"},"pascalCase":{"unsafeName":"Actor","safeName":"Actor"}},"wireValue":"actor"},"shape":{"_type":"singleProperty","name":{"name":{"originalName":"value","camelCase":{"unsafeName":"value","safeName":"value"},"snakeCase":{"unsafeName":"value","safeName":"value"},"screamingSnakeCase":{"unsafeName":"VALUE","safeName":"VALUE"},"pascalCase":{"unsafeName":"Value","safeName":"Value"}},"wireValue":"value"},"type":{"_type":"named","name":{"originalName":"ActorId","camelCase":{"unsafeName":"actorId","safeName":"actorId"},"snakeCase":{"unsafeName":"actor_id","safeName":"actor_id"},"screamingSnakeCase":{"unsafeName":"ACTOR_ID","safeName":"ACTOR_ID"},"pascalCase":{"unsafeName":"ActorId","safeName":"ActorId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:ActorId","default":null,"inline":null}},"displayName":null,"availability":null,"docs":null},{"discriminantValue":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"shape":{"_type":"samePropertiesAsObject","name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"typeId":"type_director:Director"},"displayName":null,"availability":null,"docs":null},{"discriminantValue":{"name":{"originalName":"producer","camelCase":{"unsafeName":"producer","safeName":"producer"},"snakeCase":{"unsafeName":"producer","safeName":"producer"},"screamingSnakeCase":{"unsafeName":"PRODUCER","safeName":"PRODUCER"},"pascalCase":{"unsafeName":"Producer","safeName":"Producer"}},"wireValue":"producer"},"shape":{"_type":"samePropertiesAsObject","name":{"originalName":"EmptyObject","camelCase":{"unsafeName":"emptyObject","safeName":"emptyObject"},"snakeCase":{"unsafeName":"empty_object","safeName":"empty_object"},"screamingSnakeCase":{"unsafeName":"EMPTY_OBJECT","safeName":"EMPTY_OBJECT"},"pascalCase":{"unsafeName":"EmptyObject","safeName":"EmptyObject"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:EmptyObject"},"displayName":null,"availability":null,"docs":null},{"discriminantValue":{"name":{"originalName":"cinematographer","camelCase":{"unsafeName":"cinematographer","safeName":"cinematographer"},"snakeCase":{"unsafeName":"cinematographer","safeName":"cinematographer"},"screamingSnakeCase":{"unsafeName":"CINEMATOGRAPHER","safeName":"CINEMATOGRAPHER"},"pascalCase":{"unsafeName":"Cinematographer","safeName":"Cinematographer"}},"wireValue":"cinematographer"},"shape":{"_type":"samePropertiesAsObject","name":{"originalName":"EmptyObject","camelCase":{"unsafeName":"emptyObject","safeName":"emptyObject"},"snakeCase":{"unsafeName":"empty_object","safeName":"empty_object"},"screamingSnakeCase":{"unsafeName":"EMPTY_OBJECT","safeName":"EMPTY_OBJECT"},"pascalCase":{"unsafeName":"EmptyObject","safeName":"EmptyObject"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:EmptyObject"},"displayName":null,"availability":null,"docs":"i am docs"}]},"referencedTypes":["type_imdb:ActorId","type_director:Director","type_director:Age","type_imdb:EmptyObject"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":{"originalName":"PersonExample1","camelCase":{"unsafeName":"personExample1","safeName":"personExample1"},"snakeCase":{"unsafeName":"person_example_1","safeName":"person_example_1"},"screamingSnakeCase":{"unsafeName":"PERSON_EXAMPLE_1","safeName":"PERSON_EXAMPLE_1"},"pascalCase":{"unsafeName":"PersonExample1","safeName":"PersonExample1"}},"shape":{"type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"singleUnionType":{"wireDiscriminantValue":{"name":{"originalName":"actor","camelCase":{"unsafeName":"actor","safeName":"actor"},"snakeCase":{"unsafeName":"actor","safeName":"actor"},"screamingSnakeCase":{"unsafeName":"ACTOR","safeName":"ACTOR"},"pascalCase":{"unsafeName":"Actor","safeName":"Actor"}},"wireValue":"actor"},"shape":{"type":"singleProperty","shape":{"type":"named","typeName":{"typeId":"type_imdb:ActorId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"ActorId","camelCase":{"unsafeName":"actorId","safeName":"actorId"},"snakeCase":{"unsafeName":"actor_id","safeName":"actor_id"},"screamingSnakeCase":{"unsafeName":"ACTOR_ID","safeName":"ACTOR_ID"},"pascalCase":{"unsafeName":"ActorId","safeName":"ActorId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Matt Damon"}}},"jsonExample":"Matt Damon"}}},"jsonExample":"Matt Damon"}}},"jsonExample":{"type":"actor","value":"Matt Damon"},"docs":"this is a person example"},{"name":null,"shape":{"type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"singleUnionType":{"wireDiscriminantValue":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"wireValue":"director"},"shape":{"type":"samePropertiesAsObject","typeId":"type_director:Director","object":{"properties":[{"name":{"name":{"originalName":"name","camelCase":{"unsafeName":"name","safeName":"name"},"snakeCase":{"unsafeName":"name","safeName":"name"},"screamingSnakeCase":{"unsafeName":"NAME","safeName":"NAME"},"pascalCase":{"unsafeName":"Name","safeName":"Name"}},"wireValue":"name"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"George the Directory"}}},"jsonExample":"George the Directory"},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}},{"name":{"name":{"originalName":"age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}},"wireValue":"age"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_director:Age","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Age","camelCase":{"unsafeName":"age","safeName":"age"},"snakeCase":{"unsafeName":"age","safeName":"age"},"screamingSnakeCase":{"unsafeName":"AGE","safeName":"AGE"},"pascalCase":{"unsafeName":"Age","safeName":"Age"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"integer","integer":100}},"jsonExample":100}}},"jsonExample":100},"originalTypeDeclaration":{"typeId":"type_director:Director","fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"name":{"originalName":"Director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}}}]}}}},"jsonExample":{"type":"director","name":"George the Directory","age":100},"docs":null},{"name":null,"shape":{"type":"union","discriminant":{"name":{"originalName":"type","camelCase":{"unsafeName":"type","safeName":"type"},"snakeCase":{"unsafeName":"type","safeName":"type"},"screamingSnakeCase":{"unsafeName":"TYPE","safeName":"TYPE"},"pascalCase":{"unsafeName":"Type","safeName":"Type"}},"wireValue":"type"},"singleUnionType":{"wireDiscriminantValue":{"name":{"originalName":"producer","camelCase":{"unsafeName":"producer","safeName":"producer"},"snakeCase":{"unsafeName":"producer","safeName":"producer"},"screamingSnakeCase":{"unsafeName":"PRODUCER","safeName":"PRODUCER"},"pascalCase":{"unsafeName":"Producer","safeName":"Producer"}},"wireValue":"producer"},"shape":{"type":"samePropertiesAsObject","typeId":"type_imdb:EmptyObject","object":{"properties":[]}}}},"jsonExample":{"type":"producer"},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:UnknownRequest":{"inline":false,"name":{"name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest"},"shape":{"_type":"object","extends":[],"properties":[{"name":{"name":{"originalName":"unknown","camelCase":{"unsafeName":"unknown","safeName":"unknown"},"snakeCase":{"unsafeName":"unknown","safeName":"unknown"},"screamingSnakeCase":{"unsafeName":"UNKNOWN","safeName":"UNKNOWN"},"pascalCase":{"unsafeName":"Unknown","safeName":"Unknown"}},"wireValue":"unknown"},"valueType":{"_type":"unknown"},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[]},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null},"type_imdb:RecursiveType":{"inline":false,"name":{"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType"},"shape":{"_type":"object","extends":[{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}],"properties":[{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"valueType":{"_type":"container","container":{"_type":"list","list":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"availability":null,"docs":null},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"valueType":{"_type":"container","container":{"_type":"list","list":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"availability":null,"docs":null}]},"referencedTypes":["type_imdb:CreateMovieRequest","type_imdb:RecursiveType"],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"The Godfather"}}},"jsonExample":"The Godfather"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":5}},"jsonExample":5},{"shape":{"type":"primitive","primitive":{"type":"double","double":9}},"jsonExample":9}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[10,5,9]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"named","typeName":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"The Godfather II"}}},"jsonExample":"The Godfather II"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":11}},"jsonExample":11}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[10,11]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[]},"originalTypeDeclaration":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}}}]}},"jsonExample":{"title":"The Godfather II","ratings":[10,11],"selfReferencing":[]}},{"shape":{"type":"named","typeName":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"The Godfather III"}}},"jsonExample":"The Godfather III"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[]},"originalTypeDeclaration":{"typeId":"type_imdb:RecursiveType","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}}}}]}},"jsonExample":{"title":"The Godfather III","ratings":[],"selfReferencing":[]}}],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[{"title":"The Godfather II","ratings":[10,11],"selfReferencing":[]},{"title":"The Godfather III","ratings":[],"selfReferencing":[]}]},"originalTypeDeclaration":{"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType"}}]},"jsonExample":{"title":"The Godfather","ratings":[10,5,9],"selfReferencing":[{"title":"The Godfather II","ratings":[10,11],"selfReferencing":[]},{"title":"The Godfather III","ratings":[],"selfReferencing":[]}]},"docs":null},{"name":null,"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Goodfellas"}}},"jsonExample":"Goodfellas"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},{"name":{"name":{"originalName":"selfReferencing","camelCase":{"unsafeName":"selfReferencing","safeName":"selfReferencing"},"snakeCase":{"unsafeName":"self_referencing","safeName":"self_referencing"},"screamingSnakeCase":{"unsafeName":"SELF_REFERENCING","safeName":"SELF_REFERENCING"},"pascalCase":{"unsafeName":"SelfReferencing","safeName":"SelfReferencing"}},"wireValue":"selfReferencing"},"value":{"shape":{"type":"container","container":{"type":"list","list":[],"itemType":{"_type":"named","name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType","default":null,"inline":null}}},"jsonExample":[]},"originalTypeDeclaration":{"name":{"originalName":"RecursiveType","camelCase":{"unsafeName":"recursiveType","safeName":"recursiveType"},"snakeCase":{"unsafeName":"recursive_type","safeName":"recursive_type"},"screamingSnakeCase":{"unsafeName":"RECURSIVE_TYPE","safeName":"RECURSIVE_TYPE"},"pascalCase":{"unsafeName":"RecursiveType","safeName":"RecursiveType"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:RecursiveType"}}]},"jsonExample":{"title":"Goodfellas","ratings":[1,2,3],"selfReferencing":[]},"docs":null}],"autogeneratedExamples":[],"availability":null,"docs":null}},"errors":{"error_commons:BadRequestError":{"name":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"discriminantValue":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"wireValue":"BadRequestError"},"statusCode":400,"type":null,"examples":[],"docs":null},"error_imdb:NotFoundError":{"name":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"errorId":"error_imdb:NotFoundError"},"discriminantValue":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"wireValue":"NotFoundError"},"statusCode":404,"type":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"examples":[],"docs":null}},"services":{"service_imdb":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_imdb.internalEndpoint","name":{"originalName":"internalEndpoint","camelCase":{"unsafeName":"internalEndpoint","safeName":"internalEndpoint"},"snakeCase":{"unsafeName":"internal_endpoint","safeName":"internal_endpoint"},"screamingSnakeCase":{"unsafeName":"INTERNAL_ENDPOINT","safeName":"INTERNAL_ENDPOINT"},"pascalCase":{"unsafeName":"InternalEndpoint","safeName":"InternalEndpoint"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies"}]},"pathParameters":[],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"queryParameters":[],"headers":[],"requestBody":{"type":"reference","requestBodyType":{"_type":"named","name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest","default":null,"inline":null},"contentType":null,"docs":null},"sdkRequest":{"shape":{"type":"justRequestBody","value":{"type":"typeReference","requestBodyType":{"_type":"named","name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest","default":null,"inline":null},"contentType":null,"docs":null}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"json","value":{"type":"response","responseBodyType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"docs":null}},"status-code":null},"errors":[{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[],"autogeneratedExamples":[{"example":{"id":"6ddea0588fd84ef0a9732ab58c4e1d50078da707","url":"/test/rootPathParam/movies","name":null,"endpointHeaders":[],"endpointPathParameters":[],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":{"type":"reference","shape":{"type":"named","shape":{"type":"object","properties":[{"name":{"name":{"originalName":"unknown","camelCase":{"unsafeName":"unknown","safeName":"unknown"},"snakeCase":{"unsafeName":"unknown","safeName":"unknown"},"screamingSnakeCase":{"unsafeName":"UNKNOWN","safeName":"UNKNOWN"},"pascalCase":{"unsafeName":"Unknown","safeName":"Unknown"}},"wireValue":"unknown"},"originalTypeDeclaration":{"name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest"},"value":{"shape":{"type":"unknown","unknown":{"key":"value"}},"jsonExample":{"key":"value"}}}]},"typeName":{"name":{"originalName":"UnknownRequest","camelCase":{"unsafeName":"unknownRequest","safeName":"unknownRequest"},"snakeCase":{"unsafeName":"unknown_request","safeName":"unknown_request"},"screamingSnakeCase":{"unsafeName":"UNKNOWN_REQUEST","safeName":"UNKNOWN_REQUEST"},"pascalCase":{"unsafeName":"UnknownRequest","safeName":"UnknownRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:UnknownRequest"}},"jsonExample":{"unknown":{"key":"value"}}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"string"}}},"jsonExample":"string"}}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_imdb.createMovie","name":{"originalName":"createMovie","camelCase":{"unsafeName":"createMovie","safeName":"createMovie"},"snakeCase":{"unsafeName":"create_movie","safeName":"create_movie"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE","safeName":"CREATE_MOVIE"},"pascalCase":{"unsafeName":"CreateMovie","safeName":"CreateMovie"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"","parts":[]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies"}]},"pathParameters":[],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"queryParameters":[],"headers":[],"requestBody":{"type":"reference","requestBodyType":{"_type":"named","name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest","default":null,"inline":null},"contentType":null,"docs":null},"sdkRequest":{"shape":{"type":"justRequestBody","value":{"type":"typeReference","requestBodyType":{"_type":"named","name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest","default":null,"inline":null},"contentType":null,"docs":null}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"json","value":{"type":"response","responseBodyType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"docs":null}},"status-code":null},"errors":[{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[{"example":{"id":"0ec9625807680a71388bf490199238a7c22569a5bf7092901fcb77bdd393e496","name":null,"url":"/test/root/movies","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[],"request":{"type":"reference","shape":{"type":"named","typeName":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Shrek"}}},"jsonExample":"Shrek"},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[10,10,10,10]},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}}]}},"jsonExample":{"title":"Shrek","ratings":[10,10,10,10]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"shrek-123"}}},"jsonExample":"shrek-123"}}},"jsonExample":"shrek-123"}}},"docs":null},"codeSamples":null},{"example":{"id":"624277beb517e6d1281e37e2781b35851d2d5de808ab821d6bad4c0917b014a5","name":null,"url":"/test/root/movies","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[],"request":{"type":"reference","shape":{"type":"named","typeName":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Winnie the Pooh"}}},"jsonExample":"Winnie the Pooh"},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1}},"jsonExample":1},{"shape":{"type":"primitive","primitive":{"type":"double","double":2}},"jsonExample":2},{"shape":{"type":"primitive","primitive":{"type":"double","double":3}},"jsonExample":3}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1,2,3]},"originalTypeDeclaration":{"typeId":"type_imdb:CreateMovieRequest","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}}}}]}},"jsonExample":{"title":"Winnie the Pooh","ratings":[1,2,3]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"shrek-123"}}},"jsonExample":"shrek-123"}}},"jsonExample":"shrek-123"}}},"docs":null},"codeSamples":null}],"autogeneratedExamples":[{"example":{"id":"3435f6402495ee7805cf225e15f28f07a6f16235","url":"/test/rootPathParam/movies","name":null,"endpointHeaders":[],"endpointPathParameters":[],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":{"type":"reference","shape":{"type":"named","shape":{"type":"object","properties":[{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"title"}}},"jsonExample":"title"}},{"name":{"name":{"originalName":"ratings","camelCase":{"unsafeName":"ratings","safeName":"ratings"},"snakeCase":{"unsafeName":"ratings","safeName":"ratings"},"screamingSnakeCase":{"unsafeName":"RATINGS","safeName":"RATINGS"},"pascalCase":{"unsafeName":"Ratings","safeName":"Ratings"}},"wireValue":"ratings"},"originalTypeDeclaration":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"},"value":{"shape":{"type":"container","container":{"type":"list","list":[{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1},{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1}],"itemType":{"_type":"primitive","primitive":{"v1":"DOUBLE","v2":{"type":"double","default":null,"validation":null}}}}},"jsonExample":[1.1,1.1]}}]},"typeName":{"name":{"originalName":"CreateMovieRequest","camelCase":{"unsafeName":"createMovieRequest","safeName":"createMovieRequest"},"snakeCase":{"unsafeName":"create_movie_request","safeName":"create_movie_request"},"screamingSnakeCase":{"unsafeName":"CREATE_MOVIE_REQUEST","safeName":"CREATE_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"CreateMovieRequest","safeName":"CreateMovieRequest"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:CreateMovieRequest"}},"jsonExample":{"title":"title","ratings":[1.1,1.1]}},"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"string"}}},"jsonExample":"string"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"string"}}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_imdb.getMovie","name":{"originalName":"getMovie","camelCase":{"unsafeName":"getMovie","safeName":"getMovie"},"snakeCase":{"unsafeName":"get_movie","safeName":"get_movie"},"screamingSnakeCase":{"unsafeName":"GET_MOVIE","safeName":"GET_MOVIE"},"pascalCase":{"unsafeName":"GetMovie","safeName":"GetMovie"}},"displayName":"Get Movie by Id","auth":false,"idempotent":false,"baseUrl":null,"method":"GET","basePath":null,"path":{"head":"/","parts":[{"pathParameter":"movieId","tail":""}]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies/"},{"pathParameter":"movieId","tail":""}]},"pathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null},{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"allowMultiple":true,"availability":null,"docs":null}],"headers":[],"requestBody":null,"sdkRequest":{"shape":{"type":"wrapper","wrapperName":{"originalName":"GetMovieRequest","camelCase":{"unsafeName":"getMovieRequest","safeName":"getMovieRequest"},"snakeCase":{"unsafeName":"get_movie_request","safeName":"get_movie_request"},"screamingSnakeCase":{"unsafeName":"GET_MOVIE_REQUEST","safeName":"GET_MOVIE_REQUEST"},"pascalCase":{"unsafeName":"GetMovieRequest","safeName":"GetMovieRequest"}},"bodyKey":{"originalName":"body","camelCase":{"unsafeName":"body","safeName":"body"},"snakeCase":{"unsafeName":"body","safeName":"body"},"screamingSnakeCase":{"unsafeName":"BODY","safeName":"BODY"},"pascalCase":{"unsafeName":"Body","safeName":"Body"}},"includePathParameters":false,"onlyPathParameters":false},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"json","value":{"type":"response","responseBodyType":{"_type":"named","name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie","default":null,"inline":null},"docs":null}},"status-code":null},"errors":[{"error":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"errorId":"error_imdb:NotFoundError"},"docs":null},{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[{"example":{"id":"b631e6a0d36395e53918c8b8aa048cf1bfe8180757f41402426ec3c109737ddd","name":null,"url":"/test/root/movies/id-123","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}}},"jsonExample":"id-123"}}],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"hello"}}},"jsonExample":"hello"},"shape":{"type":"exploded"}}],"request":null,"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}},"shape":{"type":"object","properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}}},"jsonExample":"id-123"},"originalTypeDeclaration":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}}},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"Shrek"}}},"jsonExample":"Shrek"},"originalTypeDeclaration":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}}},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"value":{"shape":{"type":"primitive","primitive":{"type":"double","double":10}},"jsonExample":10},"originalTypeDeclaration":{"typeId":"type_imdb:Movie","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}}}}]}},"jsonExample":{"id":"id-123","title":"Shrek","rating":10}}}},"docs":null},"codeSamples":null},{"example":{"id":"e0f04d26bcd1b030581f6d67cbd9d4be232fc0bc43bad08ffb1adb038ecbce8f","name":null,"url":"/test/root/movies/id-123","rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}}],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","typeName":{"typeId":"type_imdb:MovieId","fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}}},"shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}}},"jsonExample":"id-123"}}],"servicePathParameters":[],"endpointHeaders":[],"serviceHeaders":[],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"hello"}}},"jsonExample":"hello"},"shape":{"type":"exploded"}}],"request":null,"response":{"type":"error","error":{"name":{"originalName":"NotFoundError","camelCase":{"unsafeName":"notFoundError","safeName":"notFoundError"},"snakeCase":{"unsafeName":"not_found_error","safeName":"not_found_error"},"screamingSnakeCase":{"unsafeName":"NOT_FOUND_ERROR","safeName":"NOT_FOUND_ERROR"},"pascalCase":{"unsafeName":"NotFoundError","safeName":"NotFoundError"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"errorId":"error_imdb:NotFoundError"},"body":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id-123"}}},"jsonExample":"id-123"}},"docs":null},"codeSamples":null}],"autogeneratedExamples":[{"example":{"id":"3285e2d8c07bd6f2c3100d3de09fb7039f7d4df8","url":"/test/rootPathParam/movies/movieId","name":null,"endpointHeaders":[],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"movieId"}}},"jsonExample":"movieId"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"movieId"}}],"queryParameters":[{"name":{"name":{"originalName":"movieName","camelCase":{"unsafeName":"movieName","safeName":"movieName"},"snakeCase":{"unsafeName":"movie_name","safeName":"movie_name"},"screamingSnakeCase":{"unsafeName":"MOVIE_NAME","safeName":"MOVIE_NAME"},"pascalCase":{"unsafeName":"MovieName","safeName":"MovieName"}},"wireValue":"movieName"},"shape":{"type":"exploded"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"movieName"}}},"jsonExample":"movieName"}}],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":null,"response":{"type":"ok","value":{"type":"body","value":{"shape":{"type":"named","shape":{"type":"object","properties":[{"name":{"name":{"originalName":"id","camelCase":{"unsafeName":"id","safeName":"id"},"snakeCase":{"unsafeName":"id","safeName":"id"},"screamingSnakeCase":{"unsafeName":"ID","safeName":"ID"},"pascalCase":{"unsafeName":"Id","safeName":"Id"}},"wireValue":"id"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"id"}}},"jsonExample":"id"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"id"}},{"name":{"name":{"originalName":"title","camelCase":{"unsafeName":"title","safeName":"title"},"snakeCase":{"unsafeName":"title","safeName":"title"},"screamingSnakeCase":{"unsafeName":"TITLE","safeName":"TITLE"},"pascalCase":{"unsafeName":"Title","safeName":"Title"}},"wireValue":"title"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"title"}}},"jsonExample":"title"}},{"name":{"name":{"originalName":"rating","camelCase":{"unsafeName":"rating","safeName":"rating"},"snakeCase":{"unsafeName":"rating","safeName":"rating"},"screamingSnakeCase":{"unsafeName":"RATING","safeName":"RATING"},"pascalCase":{"unsafeName":"Rating","safeName":"Rating"}},"wireValue":"rating"},"originalTypeDeclaration":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"},"value":{"shape":{"type":"primitive","primitive":{"type":"double","double":1.1}},"jsonExample":1.1}}]},"typeName":{"name":{"originalName":"Movie","camelCase":{"unsafeName":"movie","safeName":"movie"},"snakeCase":{"unsafeName":"movie","safeName":"movie"},"screamingSnakeCase":{"unsafeName":"MOVIE","safeName":"MOVIE"},"pascalCase":{"unsafeName":"Movie","safeName":"Movie"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:Movie"}},"jsonExample":{"id":"id","title":"title","rating":1.1}}}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_imdb.delete","name":{"originalName":"delete","camelCase":{"unsafeName":"delete","safeName":"delete"},"snakeCase":{"unsafeName":"delete","safeName":"delete"},"screamingSnakeCase":{"unsafeName":"DELETE","safeName":"DELETE"},"pascalCase":{"unsafeName":"Delete","safeName":"Delete"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"DELETE","basePath":null,"path":{"head":"/","parts":[{"pathParameter":"movieId","tail":""}]},"fullPath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":"/movies/"},{"pathParameter":"movieId","tail":""}]},"pathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"allPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null},{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"valueType":{"_type":"named","name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId","default":null,"inline":null},"location":"ENDPOINT","variable":null,"docs":null}],"queryParameters":[],"headers":[],"requestBody":null,"sdkRequest":null,"response":{"body":null,"status-code":null},"errors":[{"error":{"name":{"originalName":"BadRequestError","camelCase":{"unsafeName":"badRequestError","safeName":"badRequestError"},"snakeCase":{"unsafeName":"bad_request_error","safeName":"bad_request_error"},"screamingSnakeCase":{"unsafeName":"BAD_REQUEST_ERROR","safeName":"BAD_REQUEST_ERROR"},"pascalCase":{"unsafeName":"BadRequestError","safeName":"BadRequestError"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"errorId":"error_commons:BadRequestError"},"docs":null}],"userSpecifiedExamples":[],"autogeneratedExamples":[{"example":{"id":"b45fd34c7d591a65682197d8d7b5b726c7e5a1f8","url":"/test/rootPathParam/movies/movieId","name":null,"endpointHeaders":[],"endpointPathParameters":[{"name":{"originalName":"movieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"movieId"}}},"jsonExample":"movieId"}},"typeName":{"name":{"originalName":"MovieId","camelCase":{"unsafeName":"movieId","safeName":"movieId"},"snakeCase":{"unsafeName":"movie_id","safeName":"movie_id"},"screamingSnakeCase":{"unsafeName":"MOVIE_ID","safeName":"MOVIE_ID"},"pascalCase":{"unsafeName":"MovieId","safeName":"MovieId"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"typeId":"type_imdb:MovieId"}},"jsonExample":"movieId"}}],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"rootPathParam"}}},"jsonExample":"rootPathParam"}}],"request":null,"response":{"type":"ok","value":{"type":"body","value":null}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"property","discriminant":{"name":{"originalName":"error","camelCase":{"unsafeName":"error","safeName":"error"},"snakeCase":{"unsafeName":"error","safeName":"error"},"screamingSnakeCase":{"unsafeName":"ERROR","safeName":"ERROR"},"pascalCase":{"unsafeName":"Error","safeName":"Error"}},"wireValue":"error"},"contentProperty":{"name":{"originalName":"content","camelCase":{"unsafeName":"content","safeName":"content"},"snakeCase":{"unsafeName":"content","safeName":"content"},"screamingSnakeCase":{"unsafeName":"CONTENT","safeName":"CONTENT"},"pascalCase":{"unsafeName":"Content","safeName":"Content"}},"wireValue":"content"}},"basePath":{"head":"/test/","parts":[{"pathParameter":"rootPathParam","tail":""}]},"pathParameters":[{"name":{"originalName":"rootPathParam","camelCase":{"unsafeName":"rootPathParam","safeName":"rootPathParam"},"snakeCase":{"unsafeName":"root_path_param","safeName":"root_path_param"},"screamingSnakeCase":{"unsafeName":"ROOT_PATH_PARAM","safeName":"ROOT_PATH_PARAM"},"pascalCase":{"unsafeName":"RootPathParam","safeName":"RootPathParam"}},"valueType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"location":"ROOT","variable":null,"docs":null}],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{"service_imdb":["type_imdb:MovieId","type_imdb:Movie","type_imdb:CreateMovieRequest","type_imdb:UnknownRequest"]},"sharedTypes":["type_commons:Internal","type_commons:Type","type_commons:UndiscriminatedUnion","type_director:Director","type_director:Age","type_director:LiteralBoolean","type_director:LiteralString","type_imdb:CurrencyAmount","type_imdb:ActorId","type_imdb:DirectorWrapper","type_imdb:EmptyObject","type_imdb:Person","type_imdb:RecursiveType"]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_commons":{"name":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"service":null,"types":["type_commons:Internal","type_commons:Type","type_commons:UndiscriminatedUnion"],"errors":["error_commons:BadRequestError"],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_director":{"name":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}},"fernFilepath":{"allParts":[{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}],"packagePath":[],"file":{"originalName":"director","camelCase":{"unsafeName":"director","safeName":"director"},"snakeCase":{"unsafeName":"director","safeName":"director"},"screamingSnakeCase":{"unsafeName":"DIRECTOR","safeName":"DIRECTOR"},"pascalCase":{"unsafeName":"Director","safeName":"Director"}}},"service":null,"types":["type_director:Director","type_director:Age","type_director:LiteralBoolean","type_director:LiteralString"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_imdb":{"name":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}},"fernFilepath":{"allParts":[{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}],"packagePath":[],"file":{"originalName":"imdb","camelCase":{"unsafeName":"imdb","safeName":"imdb"},"snakeCase":{"unsafeName":"imdb","safeName":"imdb"},"screamingSnakeCase":{"unsafeName":"IMDB","safeName":"IMDB"},"pascalCase":{"unsafeName":"Imdb","safeName":"Imdb"}}},"service":"service_imdb","types":["type_imdb:CurrencyAmount","type_imdb:MovieId","type_imdb:ActorId","type_imdb:Movie","type_imdb:CreateMovieRequest","type_imdb:DirectorWrapper","type_imdb:EmptyObject","type_imdb:Person","type_imdb:UnknownRequest","type_imdb:RecursiveType"],"errors":["error_imdb:NotFoundError"],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_commons","subpackage_director","subpackage_imdb"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; -exports[`ir > {"name":"streaming"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":null,"auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[],"idempotencyHeaders":[],"types":{},"errors":{},"services":{"service_streaming":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}],"packagePath":[],"file":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_streaming.streaming","name":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"/streaming","parts":[]},"fullPath":{"head":"/movies/streaming","parts":[]},"pathParameters":[],"allPathParameters":[],"queryParameters":[],"headers":[],"requestBody":null,"sdkRequest":null,"response":{"body":{"type":"streaming","value":{"type":"json","payload":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"terminator":null,"docs":null}},"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_streaming.maybeStreaming","name":{"originalName":"maybeStreaming","camelCase":{"unsafeName":"maybeStreaming","safeName":"maybeStreaming"},"snakeCase":{"unsafeName":"maybe_streaming","safeName":"maybe_streaming"},"screamingSnakeCase":{"unsafeName":"MAYBE_STREAMING","safeName":"MAYBE_STREAMING"},"pascalCase":{"unsafeName":"MaybeStreaming","safeName":"MaybeStreaming"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"/maybe-streaming","parts":[]},"fullPath":{"head":"/movies/maybe-streaming","parts":[]},"pathParameters":[],"allPathParameters":[],"queryParameters":[],"headers":[],"requestBody":{"type":"inlinedRequestBody","name":{"originalName":"maybeStreamingRequest","camelCase":{"unsafeName":"maybeStreamingRequest","safeName":"maybeStreamingRequest"},"snakeCase":{"unsafeName":"maybe_streaming_request","safeName":"maybe_streaming_request"},"screamingSnakeCase":{"unsafeName":"MAYBE_STREAMING_REQUEST","safeName":"MAYBE_STREAMING_REQUEST"},"pascalCase":{"unsafeName":"MaybeStreamingRequest","safeName":"MaybeStreamingRequest"}},"extends":[],"contentType":null,"properties":[{"name":{"name":{"originalName":"stream","camelCase":{"unsafeName":"stream","safeName":"stream"},"snakeCase":{"unsafeName":"stream","safeName":"stream"},"screamingSnakeCase":{"unsafeName":"STREAM","safeName":"STREAM"},"pascalCase":{"unsafeName":"Stream","safeName":"Stream"}},"wireValue":"stream"},"valueType":{"_type":"primitive","primitive":{"v1":"BOOLEAN","v2":null}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[],"docs":null},"sdkRequest":{"shape":{"type":"wrapper","wrapperName":{"originalName":"maybeStreamingRequest","camelCase":{"unsafeName":"maybeStreamingRequest","safeName":"maybeStreamingRequest"},"snakeCase":{"unsafeName":"maybe_streaming_request","safeName":"maybe_streaming_request"},"screamingSnakeCase":{"unsafeName":"MAYBE_STREAMING_REQUEST","safeName":"MAYBE_STREAMING_REQUEST"},"pascalCase":{"unsafeName":"MaybeStreamingRequest","safeName":"MaybeStreamingRequest"}},"bodyKey":{"originalName":"body","camelCase":{"unsafeName":"body","safeName":"body"},"snakeCase":{"unsafeName":"body","safeName":"body"},"screamingSnakeCase":{"unsafeName":"BODY","safeName":"BODY"},"pascalCase":{"unsafeName":"Body","safeName":"Body"}}},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"streaming","value":{"type":"json","payload":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"terminator":"[DONE]","docs":null}},"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"statusCode"},"basePath":null,"pathParameters":[],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{},"sharedTypes":[]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_streaming":{"name":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}},"fernFilepath":{"allParts":[{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}],"packagePath":[],"file":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}},"service":"service_streaming","types":[],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_streaming"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":true,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; +exports[`ir > {"name":"streaming"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":null,"auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[],"idempotencyHeaders":[],"types":{},"errors":{},"services":{"service_streaming":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}],"packagePath":[],"file":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_streaming.streaming","name":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"/streaming","parts":[]},"fullPath":{"head":"/movies/streaming","parts":[]},"pathParameters":[],"allPathParameters":[],"queryParameters":[],"headers":[],"requestBody":null,"sdkRequest":null,"response":{"body":{"type":"streaming","value":{"type":"json","payload":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"terminator":null,"docs":null}},"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[],"pagination":null,"transport":null,"availability":null,"docs":null},{"id":"endpoint_streaming.maybeStreaming","name":{"originalName":"maybeStreaming","camelCase":{"unsafeName":"maybeStreaming","safeName":"maybeStreaming"},"snakeCase":{"unsafeName":"maybe_streaming","safeName":"maybe_streaming"},"screamingSnakeCase":{"unsafeName":"MAYBE_STREAMING","safeName":"MAYBE_STREAMING"},"pascalCase":{"unsafeName":"MaybeStreaming","safeName":"MaybeStreaming"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"/maybe-streaming","parts":[]},"fullPath":{"head":"/movies/maybe-streaming","parts":[]},"pathParameters":[],"allPathParameters":[],"queryParameters":[],"headers":[],"requestBody":{"type":"inlinedRequestBody","name":{"originalName":"maybeStreamingRequest","camelCase":{"unsafeName":"maybeStreamingRequest","safeName":"maybeStreamingRequest"},"snakeCase":{"unsafeName":"maybe_streaming_request","safeName":"maybe_streaming_request"},"screamingSnakeCase":{"unsafeName":"MAYBE_STREAMING_REQUEST","safeName":"MAYBE_STREAMING_REQUEST"},"pascalCase":{"unsafeName":"MaybeStreamingRequest","safeName":"MaybeStreamingRequest"}},"extends":[],"contentType":null,"properties":[{"name":{"name":{"originalName":"stream","camelCase":{"unsafeName":"stream","safeName":"stream"},"snakeCase":{"unsafeName":"stream","safeName":"stream"},"screamingSnakeCase":{"unsafeName":"STREAM","safeName":"STREAM"},"pascalCase":{"unsafeName":"Stream","safeName":"Stream"}},"wireValue":"stream"},"valueType":{"_type":"primitive","primitive":{"v1":"BOOLEAN","v2":null}},"availability":null,"docs":null}],"extra-properties":false,"extendedProperties":[],"docs":null},"sdkRequest":{"shape":{"type":"wrapper","wrapperName":{"originalName":"maybeStreamingRequest","camelCase":{"unsafeName":"maybeStreamingRequest","safeName":"maybeStreamingRequest"},"snakeCase":{"unsafeName":"maybe_streaming_request","safeName":"maybe_streaming_request"},"screamingSnakeCase":{"unsafeName":"MAYBE_STREAMING_REQUEST","safeName":"MAYBE_STREAMING_REQUEST"},"pascalCase":{"unsafeName":"MaybeStreamingRequest","safeName":"MaybeStreamingRequest"}},"bodyKey":{"originalName":"body","camelCase":{"unsafeName":"body","safeName":"body"},"snakeCase":{"unsafeName":"body","safeName":"body"},"screamingSnakeCase":{"unsafeName":"BODY","safeName":"BODY"},"pascalCase":{"unsafeName":"Body","safeName":"Body"}},"includePathParameters":false,"onlyPathParameters":false},"requestParameterName":{"originalName":"request","camelCase":{"unsafeName":"request","safeName":"request"},"snakeCase":{"unsafeName":"request","safeName":"request"},"screamingSnakeCase":{"unsafeName":"REQUEST","safeName":"REQUEST"},"pascalCase":{"unsafeName":"Request","safeName":"Request"}},"streamParameter":null},"response":{"body":{"type":"streaming","value":{"type":"json","payload":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"terminator":"[DONE]","docs":null}},"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"statusCode"},"basePath":null,"pathParameters":[],"variables":[],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{},"sharedTypes":[]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_streaming":{"name":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}},"fernFilepath":{"allParts":[{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}],"packagePath":[],"file":{"originalName":"streaming","camelCase":{"unsafeName":"streaming","safeName":"streaming"},"snakeCase":{"unsafeName":"streaming","safeName":"streaming"},"screamingSnakeCase":{"unsafeName":"STREAMING","safeName":"STREAMING"},"pascalCase":{"unsafeName":"Streaming","safeName":"Streaming"}}},"service":"service_streaming","types":[],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_streaming"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":true,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; exports[`ir > {"name":"variables"} 1`] = `"{"fdrApiDefinitionId":null,"apiVersion":null,"apiName":{"originalName":"my-api","camelCase":{"unsafeName":"myApi","safeName":"myApi"},"snakeCase":{"unsafeName":"my_api","safeName":"my_api"},"screamingSnakeCase":{"unsafeName":"MY_API","safeName":"MY_API"},"pascalCase":{"unsafeName":"MyApi","safeName":"MyApi"}},"apiDisplayName":null,"apiDocs":null,"auth":{"requirement":"ALL","schemes":[],"docs":null},"headers":[],"idempotencyHeaders":[],"types":{"type_commons:StringAlias":{"inline":false,"name":{"name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias"},"shape":{"_type":"alias","aliasOf":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}},"resolvedType":{"_type":"primitive","primitive":{"v1":"STRING","v2":{"type":"string","default":null,"validation":null}}}},"referencedTypes":[],"encoding":{"json":{},"proto":null},"source":null,"userProvidedExamples":[],"autogeneratedExamples":[],"availability":null,"docs":null}},"errors":{},"services":{"service_service":{"availability":null,"name":{"fernFilepath":{"allParts":[{"originalName":"service","camelCase":{"unsafeName":"service","safeName":"service"},"snakeCase":{"unsafeName":"service","safeName":"service"},"screamingSnakeCase":{"unsafeName":"SERVICE","safeName":"SERVICE"},"pascalCase":{"unsafeName":"Service","safeName":"Service"}}],"packagePath":[],"file":{"originalName":"service","camelCase":{"unsafeName":"service","safeName":"service"},"snakeCase":{"unsafeName":"service","safeName":"service"},"screamingSnakeCase":{"unsafeName":"SERVICE","safeName":"SERVICE"},"pascalCase":{"unsafeName":"Service","safeName":"Service"}}}},"displayName":null,"basePath":{"head":"/movies","parts":[]},"headers":[],"pathParameters":[],"encoding":{"json":{},"proto":null},"transport":{"type":"http"},"endpoints":[{"id":"endpoint_service.test","name":{"originalName":"test","camelCase":{"unsafeName":"test","safeName":"test"},"snakeCase":{"unsafeName":"test","safeName":"test"},"screamingSnakeCase":{"unsafeName":"TEST","safeName":"TEST"},"pascalCase":{"unsafeName":"Test","safeName":"Test"}},"displayName":null,"auth":false,"idempotent":false,"baseUrl":null,"method":"POST","basePath":null,"path":{"head":"/","parts":[{"pathParameter":"foo","tail":""}]},"fullPath":{"head":"/","parts":[{"pathParameter":"root","tail":"/movies/"},{"pathParameter":"foo","tail":""}]},"pathParameters":[{"name":{"originalName":"foo","camelCase":{"unsafeName":"foo","safeName":"foo"},"snakeCase":{"unsafeName":"foo","safeName":"foo"},"screamingSnakeCase":{"unsafeName":"FOO","safeName":"FOO"},"pascalCase":{"unsafeName":"Foo","safeName":"Foo"}},"valueType":{"_type":"named","name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias","default":null,"inline":null},"location":"ENDPOINT","variable":"my-variable","docs":null}],"allPathParameters":[{"name":{"originalName":"root","camelCase":{"unsafeName":"root","safeName":"root"},"snakeCase":{"unsafeName":"root","safeName":"root"},"screamingSnakeCase":{"unsafeName":"ROOT","safeName":"ROOT"},"pascalCase":{"unsafeName":"Root","safeName":"Root"}},"valueType":{"_type":"named","name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias","default":null,"inline":null},"location":"ROOT","variable":"my-variable","docs":null},{"name":{"originalName":"foo","camelCase":{"unsafeName":"foo","safeName":"foo"},"snakeCase":{"unsafeName":"foo","safeName":"foo"},"screamingSnakeCase":{"unsafeName":"FOO","safeName":"FOO"},"pascalCase":{"unsafeName":"Foo","safeName":"Foo"}},"valueType":{"_type":"named","name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias","default":null,"inline":null},"location":"ENDPOINT","variable":"my-variable","docs":null}],"queryParameters":[],"headers":[],"requestBody":null,"sdkRequest":null,"response":{"body":null,"status-code":null},"errors":[],"userSpecifiedExamples":[],"autogeneratedExamples":[{"example":{"id":"568b0fbafed694545ab88d89d14b21c8c748220a","url":"/root/movies/foo","name":null,"endpointHeaders":[],"endpointPathParameters":[{"name":{"originalName":"foo","camelCase":{"unsafeName":"foo","safeName":"foo"},"snakeCase":{"unsafeName":"foo","safeName":"foo"},"screamingSnakeCase":{"unsafeName":"FOO","safeName":"FOO"},"pascalCase":{"unsafeName":"Foo","safeName":"Foo"}},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"foo"}}},"jsonExample":"foo"}},"typeName":{"name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias"}},"jsonExample":"foo"}}],"queryParameters":[],"servicePathParameters":[],"serviceHeaders":[],"rootPathParameters":[{"name":{"originalName":"root","camelCase":{"unsafeName":"root","safeName":"root"},"snakeCase":{"unsafeName":"root","safeName":"root"},"screamingSnakeCase":{"unsafeName":"ROOT","safeName":"ROOT"},"pascalCase":{"unsafeName":"Root","safeName":"Root"}},"value":{"shape":{"type":"named","shape":{"type":"alias","value":{"shape":{"type":"primitive","primitive":{"type":"string","string":{"original":"root"}}},"jsonExample":"root"}},"typeName":{"name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias"}},"jsonExample":"root"}}],"request":null,"response":{"type":"ok","value":{"type":"body","value":null}},"docs":null}}],"pagination":null,"transport":null,"availability":null,"docs":null}]}},"constants":{"errorInstanceIdKey":{"name":{"originalName":"errorInstanceId","camelCase":{"unsafeName":"errorInstanceId","safeName":"errorInstanceId"},"snakeCase":{"unsafeName":"error_instance_id","safeName":"error_instance_id"},"screamingSnakeCase":{"unsafeName":"ERROR_INSTANCE_ID","safeName":"ERROR_INSTANCE_ID"},"pascalCase":{"unsafeName":"ErrorInstanceId","safeName":"ErrorInstanceId"}},"wireValue":"errorInstanceId"}},"environments":null,"errorDiscriminationStrategy":{"type":"statusCode"},"basePath":{"head":"/","parts":[{"pathParameter":"root","tail":""}]},"pathParameters":[{"name":{"originalName":"root","camelCase":{"unsafeName":"root","safeName":"root"},"snakeCase":{"unsafeName":"root","safeName":"root"},"screamingSnakeCase":{"unsafeName":"ROOT","safeName":"ROOT"},"pascalCase":{"unsafeName":"Root","safeName":"Root"}},"valueType":{"_type":"named","name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias","default":null,"inline":null},"location":"ROOT","variable":"my-variable","docs":null}],"variables":[{"id":"my-variable","name":{"originalName":"my-variable","camelCase":{"unsafeName":"myVariable","safeName":"myVariable"},"snakeCase":{"unsafeName":"my_variable","safeName":"my_variable"},"screamingSnakeCase":{"unsafeName":"MY_VARIABLE","safeName":"MY_VARIABLE"},"pascalCase":{"unsafeName":"MyVariable","safeName":"MyVariable"}},"type":{"_type":"named","name":{"originalName":"StringAlias","camelCase":{"unsafeName":"stringAlias","safeName":"stringAlias"},"snakeCase":{"unsafeName":"string_alias","safeName":"string_alias"},"screamingSnakeCase":{"unsafeName":"STRING_ALIAS","safeName":"STRING_ALIAS"},"pascalCase":{"unsafeName":"StringAlias","safeName":"StringAlias"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"typeId":"type_commons:StringAlias","default":null,"inline":null},"docs":"I am a variable"}],"serviceTypeReferenceInfo":{"typesReferencedOnlyByService":{"service_service":["type_commons:StringAlias"]},"sharedTypes":[]},"webhookGroups":{},"websocketChannels":{},"readmeConfig":null,"sourceConfig":null,"publishConfig":null,"subpackages":{"subpackage_commons":{"name":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}},"fernFilepath":{"allParts":[{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}],"packagePath":[],"file":{"originalName":"commons","camelCase":{"unsafeName":"commons","safeName":"commons"},"snakeCase":{"unsafeName":"commons","safeName":"commons"},"screamingSnakeCase":{"unsafeName":"COMMONS","safeName":"COMMONS"},"pascalCase":{"unsafeName":"Commons","safeName":"Commons"}}},"service":null,"types":["type_commons:StringAlias"],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":false,"docs":null},"subpackage_service":{"name":{"originalName":"service","camelCase":{"unsafeName":"service","safeName":"service"},"snakeCase":{"unsafeName":"service","safeName":"service"},"screamingSnakeCase":{"unsafeName":"SERVICE","safeName":"SERVICE"},"pascalCase":{"unsafeName":"Service","safeName":"Service"}},"fernFilepath":{"allParts":[{"originalName":"service","camelCase":{"unsafeName":"service","safeName":"service"},"snakeCase":{"unsafeName":"service","safeName":"service"},"screamingSnakeCase":{"unsafeName":"SERVICE","safeName":"SERVICE"},"pascalCase":{"unsafeName":"Service","safeName":"Service"}}],"packagePath":[],"file":{"originalName":"service","camelCase":{"unsafeName":"service","safeName":"service"},"snakeCase":{"unsafeName":"service","safeName":"service"},"screamingSnakeCase":{"unsafeName":"SERVICE","safeName":"SERVICE"},"pascalCase":{"unsafeName":"Service","safeName":"Service"}}},"service":"service_service","types":[],"errors":[],"subpackages":[],"navigationConfig":null,"webhooks":null,"websocket":null,"hasEndpointsInTree":true,"docs":null}},"rootPackage":{"fernFilepath":{"allParts":[],"packagePath":[],"file":null},"websocket":null,"service":null,"types":[],"errors":[],"subpackages":["subpackage_commons","subpackage_service"],"webhooks":null,"navigationConfig":null,"hasEndpointsInTree":true,"docs":null},"sdkConfig":{"isAuthMandatory":false,"hasStreamingEndpoints":false,"hasPaginatedEndpoints":false,"hasFileDownloadEndpoints":false,"platformHeaders":{"language":"X-Fern-Language","sdkName":"X-Fern-SDK-Name","sdkVersion":"X-Fern-SDK-Version","userAgent":null}}}"`; diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/path-parameters/type_user_User.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/path-parameters/type_user_User.json new file mode 100644 index 00000000000..ce04ebf90a7 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/path-parameters/type_user_User.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name", + "tags" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/schema/src/schemas/api/resources/service/types/HttpRequestSchema.ts b/packages/cli/fern-definition/schema/src/schemas/api/resources/service/types/HttpRequestSchema.ts index 715ab60addc..0a4eb995f7e 100644 --- a/packages/cli/fern-definition/schema/src/schemas/api/resources/service/types/HttpRequestSchema.ts +++ b/packages/cli/fern-definition/schema/src/schemas/api/resources/service/types/HttpRequestSchema.ts @@ -6,6 +6,7 @@ import * as FernDefinition from "../../../index"; export interface HttpRequestSchema extends FernDefinition.WithName, FernDefinition.WithDocsSchema { "content-type"?: string; + "path-parameters"?: Record; "query-parameters"?: Record; headers?: Record; body?: FernDefinition.HttpRequestBodySchema; diff --git a/packages/cli/fern-definition/schema/src/schemas/serialization/resources/service/types/HttpRequestSchema.ts b/packages/cli/fern-definition/schema/src/schemas/serialization/resources/service/types/HttpRequestSchema.ts index 3db9075c2be..b55a676cb09 100644 --- a/packages/cli/fern-definition/schema/src/schemas/serialization/resources/service/types/HttpRequestSchema.ts +++ b/packages/cli/fern-definition/schema/src/schemas/serialization/resources/service/types/HttpRequestSchema.ts @@ -5,6 +5,7 @@ import * as serializers from "../../../index"; import * as FernDefinition from "../../../../api/index"; import * as core from "../../../../core"; +import { HttpPathParameterSchema } from "./HttpPathParameterSchema"; import { HttpQueryParameterSchema } from "./HttpQueryParameterSchema"; import { HttpHeaderSchema } from "./HttpHeaderSchema"; import { HttpRequestBodySchema } from "./HttpRequestBodySchema"; @@ -17,6 +18,7 @@ export const HttpRequestSchema: core.serialization.ObjectSchema< > = core.serialization .object({ "content-type": core.serialization.string().optional(), + "path-parameters": core.serialization.record(core.serialization.string(), HttpPathParameterSchema).optional(), "query-parameters": core.serialization.record(core.serialization.string(), HttpQueryParameterSchema).optional(), headers: core.serialization.record(core.serialization.string(), HttpHeaderSchema).optional(), body: HttpRequestBodySchema.optional(), @@ -27,6 +29,7 @@ export const HttpRequestSchema: core.serialization.ObjectSchema< export declare namespace HttpRequestSchema { interface Raw extends WithName.Raw, WithDocsSchema.Raw { "content-type"?: string | null; + "path-parameters"?: Record | null; "query-parameters"?: Record | null; headers?: Record | null; body?: HttpRequestBodySchema.Raw | null; diff --git a/packages/cli/fern-definition/validator/src/ast/visitors/services/visitHttpService.ts b/packages/cli/fern-definition/validator/src/ast/visitors/services/visitHttpService.ts index a4b964a22ab..c07962551ee 100644 --- a/packages/cli/fern-definition/validator/src/ast/visitors/services/visitHttpService.ts +++ b/packages/cli/fern-definition/validator/src/ast/visitors/services/visitHttpService.ts @@ -105,6 +105,13 @@ async function visitEndpoint({ await visitObject(request, { name: noop, docs: createDocsVisitor(visitor, nodePathForRequest), + "path-parameters": async (pathParameters) => { + await visitPathParameters({ + pathParameters, + visitor, + nodePath: [...nodePathForRequest, "path-parameters"] + }); + }, "query-parameters": async (queryParameters) => { if (queryParameters == null) { return; diff --git a/packages/cli/fern-definition/validator/src/getAllRules.ts b/packages/cli/fern-definition/validator/src/getAllRules.ts index 8b21c641a4f..d1b952ca4a9 100644 --- a/packages/cli/fern-definition/validator/src/getAllRules.ts +++ b/packages/cli/fern-definition/validator/src/getAllRules.ts @@ -37,6 +37,7 @@ import { ValidGenericRule } from "./rules/valid-generic"; import { ValidNavigationRule } from "./rules/valid-navigation"; import { ValidOauthRule } from "./rules/valid-oauth"; import { ValidPaginationRule } from "./rules/valid-pagination"; +import { ValidPathParametersConfigurationRule } from "./rules/valid-path-parameters-configuration"; import { ValidServiceUrlsRule } from "./rules/valid-service-urls"; import { ValidStreamConditionRule } from "./rules/valid-stream-condition"; import { ValidTypeNameRule } from "./rules/valid-type-name"; @@ -89,7 +90,8 @@ export function getAllRules(): Rule[] { NoUnusedGenericRule, ValidGenericRule, CompatibleIrVersionsRule, - ContentTypeOnlyForMultipartRule + ContentTypeOnlyForMultipartRule, + ValidPathParametersConfigurationRule ]; } diff --git a/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/__test__/fixtures/simple/definition/simple.yml b/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/__test__/fixtures/simple/definition/simple.yml index 6f9b47e2858..ae611828e14 100644 --- a/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/__test__/fixtures/simple/definition/simple.yml +++ b/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/__test__/fixtures/simple/definition/simple.yml @@ -10,6 +10,14 @@ service: parameter: string method: POST + inlinedNoErrors: + path: /path/{parameter}/to/endpoint + request: + name: InlinedRequest + path-parameters: + parameter: string + method: POST + missingPathParameters: path: /path/{parameter1}/{parameter2}/to/endpoint path-parameters: diff --git a/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/no-undefined-path-parameters.ts b/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/no-undefined-path-parameters.ts index 11e8ba85037..3d63ea4c289 100644 --- a/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/no-undefined-path-parameters.ts +++ b/packages/cli/fern-definition/validator/src/rules/no-undefined-path-parameters/no-undefined-path-parameters.ts @@ -1,4 +1,4 @@ -import { constructHttpPath } from "@fern-api/ir-generator"; +import { constructHttpPath, getEndpointPathParameters } from "@fern-api/ir-generator"; import { RawSchemas } from "@fern-api/fern-definition-schema"; import chalk from "chalk"; import capitalize from "lodash-es/capitalize"; @@ -35,7 +35,7 @@ export const NoUndefinedPathParametersRule: Rule = { endpoint["base-path"] != null ? urlJoin(endpoint["base-path"], endpoint.path) : endpoint.path, - pathParameters: endpoint["path-parameters"] != null ? endpoint["path-parameters"] : {}, + pathParameters: getEndpointPathParameters(endpoint), pathType: "endpoint" }); } diff --git a/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/__test__/fixtures/endpoint-level-base-path/definition/path-parameters.yml b/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/__test__/fixtures/endpoint-level-base-path/definition/path-parameters.yml index d549db62218..9ea088f3e02 100644 --- a/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/__test__/fixtures/endpoint-level-base-path/definition/path-parameters.yml +++ b/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/__test__/fixtures/endpoint-level-base-path/definition/path-parameters.yml @@ -11,3 +11,15 @@ service: examples: - path-parameters: endpointPathParam: hello + + inlinedGet: + method: GET + base-path: "/latest" + path: /{endpointPathParam} + request: + name: InlinedRequest + path-parameters: + endpointPathParam: string + examples: + - path-parameters: + endpointPathParam: hello diff --git a/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/valid-example-endpoint-call.ts b/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/valid-example-endpoint-call.ts index cf9b5979415..fe9c378c6c0 100644 --- a/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/valid-example-endpoint-call.ts +++ b/packages/cli/fern-definition/validator/src/rules/valid-example-endpoint-call/valid-example-endpoint-call.ts @@ -1,7 +1,9 @@ +import { FernWorkspace } from "@fern-api/api-workspace-commons"; import { constructFernFileContext, ErrorResolverImpl, ExampleResolverImpl, + getEndpointPathParameters, resolvePathParameter, TypeResolverImpl, VariableResolverImpl @@ -11,6 +13,7 @@ import { CASINGS_GENERATOR } from "../../utils/casingsGenerator"; import { validateExampleEndpointCallParameters } from "./validateExampleEndpointCallParameters"; import { validateRequest } from "./validateRequest"; import { validateResponse } from "./validateResponse"; +import { RawSchemas } from "@fern-api/fern-definition-schema"; export const ValidExampleEndpointCallRule: Rule = { name: "valid-example-endpoint-call", @@ -50,14 +53,11 @@ export const ValidExampleEndpointCallRule: Rule = { { relativeFilepath, contents: definitionFile } ) => { return validateExampleEndpointCallParameters({ - allDeclarations: - endpoint["base-path"] != null - ? { ...endpoint["path-parameters"] } - : { - ...workspace.definition.rootApiFile.contents["path-parameters"], - ...service["path-parameters"], - ...endpoint["path-parameters"] - }, + allDeclarations: getAllPathParameterDeclarations({ + workspace, + service, + endpoint + }), examples, parameterDisplayName: "path parameter", typeResolver, @@ -133,3 +133,22 @@ export const ValidExampleEndpointCallRule: Rule = { }; } }; + +function getAllPathParameterDeclarations({ + workspace, + service, + endpoint +}: { + workspace: FernWorkspace; + service: RawSchemas.HttpServiceSchema; + endpoint: RawSchemas.HttpEndpointSchema; +}): Record { + const endpointPathParameters = getEndpointPathParameters(endpoint); + return endpoint["base-path"] != null + ? { ...endpointPathParameters } + : { + ...workspace.definition.rootApiFile.contents["path-parameters"], + ...service["path-parameters"], + ...endpointPathParameters + }; +} diff --git a/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/definition/api.yml b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/definition/api.yml new file mode 100644 index 00000000000..24ba87792e7 --- /dev/null +++ b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/definition/api.yml @@ -0,0 +1 @@ +name: simple-api \ No newline at end of file diff --git a/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/definition/simple.yml b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/definition/simple.yml new file mode 100644 index 00000000000..a1dc2e5892e --- /dev/null +++ b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/definition/simple.yml @@ -0,0 +1,28 @@ +service: + auth: false + base-path: /base/{baseParameter} + path-parameters: + fakeBaseParameter: string + endpoints: + legacy: + path: /path/{parameter}/to/endpoint + path-parameters: + parameter: string + method: POST + + inlined: + path: /path/{parameter}/to/endpoint + request: + name: InlinedRequest + path-parameters: + parameter: string + method: POST + + conflict: + path: /path/{parameter1}/{parameter2}/to/endpoint + path-parameters: + parameter1: string + request: + path-parameters: + parameter2: string + method: POST diff --git a/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/generators.yml b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/fixtures/simple/generators.yml @@ -0,0 +1 @@ +{} diff --git a/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/valid-path-parameters-configuration.test.ts b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/valid-path-parameters-configuration.test.ts new file mode 100644 index 00000000000..09105e51ddc --- /dev/null +++ b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/__test__/valid-path-parameters-configuration.test.ts @@ -0,0 +1,25 @@ +import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { getViolationsForRule } from "../../../testing-utils/getViolationsForRule"; +import { ValidPathParametersConfigurationRule } from "../valid-path-parameters-configuration"; + +describe("valid-path-parameters-configuration", () => { + it("simple", async () => { + const violations = await getViolationsForRule({ + rule: ValidPathParametersConfigurationRule, + absolutePathToWorkspace: join( + AbsoluteFilePath.of(__dirname), + RelativeFilePath.of("fixtures"), + RelativeFilePath.of("simple") + ) + }); + + expect(violations).toEqual([ + { + message: "path-parameters cannot be defined in both endpoint and request.", + nodePath: ["service", "endpoints", "conflict"], + relativeFilepath: RelativeFilePath.of("simple.yml"), + severity: "error" + } + ]); + }); +}); diff --git a/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/index.ts b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/index.ts new file mode 100644 index 00000000000..33d74239107 --- /dev/null +++ b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/index.ts @@ -0,0 +1 @@ +export { ValidPathParametersConfigurationRule } from "./valid-path-parameters-configuration"; diff --git a/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/valid-path-parameters-configuration.ts b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/valid-path-parameters-configuration.ts new file mode 100644 index 00000000000..dd4f467d7c5 --- /dev/null +++ b/packages/cli/fern-definition/validator/src/rules/valid-path-parameters-configuration/valid-path-parameters-configuration.ts @@ -0,0 +1,26 @@ +import { Rule } from "../../Rule"; + +export const ValidPathParametersConfigurationRule: Rule = { + name: "valid-path-parameters-configuration", + create: () => { + return { + definitionFile: { + httpEndpoint: ({ endpoint }) => { + if ( + endpoint["path-parameters"] != null && + typeof endpoint.request !== "string" && + endpoint?.request?.["path-parameters"] != null + ) { + return [ + { + severity: "error", + message: "path-parameters cannot be defined in both endpoint and request." + } + ]; + } + return []; + } + } + }; + } +}; diff --git a/packages/cli/generation/ir-generator/src/__test__/irs/availability.json b/packages/cli/generation/ir-generator/src/__test__/irs/availability.json index 2963fdef16a..89e7b3e6c70 100644 --- a/packages/cli/generation/ir-generator/src/__test__/irs/availability.json +++ b/packages/cli/generation/ir-generator/src/__test__/irs/availability.json @@ -369,7 +369,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/irs/docs.json b/packages/cli/generation/ir-generator/src/__test__/irs/docs.json index a4d21b1fea0..931ac399a99 100644 --- a/packages/cli/generation/ir-generator/src/__test__/irs/docs.json +++ b/packages/cli/generation/ir-generator/src/__test__/irs/docs.json @@ -363,7 +363,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json index 41f947c2b0a..55ef21e9745 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json @@ -562,7 +562,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json index 163ade7a393..4d67e1a129e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json @@ -792,7 +792,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json index f9f8062bfae..13f8e4de719 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json @@ -996,7 +996,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/any-auth.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/any-auth.json index 810ba8924fe..8378aefc78e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/any-auth.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/any-auth.json @@ -1007,7 +1007,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json index 5b891631c2a..c2611a82f2e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json @@ -3632,7 +3632,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json index 9ecfd284a5c..ca6498cf6fc 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json @@ -433,7 +433,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/cross-package-type-names.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/cross-package-type-names.json index 510afd51113..daec01c8930 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/cross-package-type-names.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/cross-package-type-names.json @@ -4303,7 +4303,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto-exhaustive.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto-exhaustive.json index f3cff92d830..58eb267d4d4 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto-exhaustive.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto-exhaustive.json @@ -4053,7 +4053,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -5884,7 +5886,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -6454,7 +6458,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -7964,7 +7970,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -11004,7 +11012,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -13446,7 +13456,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -23164,7 +23176,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto.json index 14a81ab53d1..8e164e2b2a7 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/csharp-grpc-proto.json @@ -1135,7 +1135,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json index 9f278a8e6bc..81d34605877 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json @@ -1007,7 +1007,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -2816,7 +2818,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -3635,7 +3639,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json index d44fc8ed566..ace70ceea6c 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json @@ -24373,7 +24373,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -32841,7 +32843,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json index 49f8e0935f4..0c28797566f 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json @@ -53361,7 +53361,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -53653,7 +53655,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -53989,7 +53993,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -57763,7 +57769,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -65652,7 +65660,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json index c8df69fbf4a..f2e07320462 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json @@ -1519,7 +1519,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json index 7e6ff31a178..a90f3d08d9f 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json @@ -519,7 +519,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json index 2ceb983dfe9..92bef297fac 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json @@ -1204,7 +1204,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -1373,7 +1375,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -1751,7 +1755,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -2157,7 +2163,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json index 138e89f0bd0..7460c9a763e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json @@ -572,7 +572,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json index 2fd3edabb70..4cc3dee3f35 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json @@ -1924,7 +1924,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -3396,7 +3398,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -6780,7 +6784,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json index 35735bfe8cf..5059184d07f 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json @@ -4555,7 +4555,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-file-directory.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-file-directory.json index ae757030344..ac81fe4fe67 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-file-directory.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-file-directory.json @@ -3281,7 +3281,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -4570,7 +4572,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -6020,7 +6024,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json index 8e18906e7c3..bec6292e2e8 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json @@ -899,7 +899,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json index 531e0ceac60..20782e9b75d 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json @@ -264,7 +264,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -575,7 +577,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json index a9e0a7eae79..87c7cd51cc7 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json @@ -264,7 +264,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -575,7 +577,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json index d152ee34db4..b27722ec738 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json @@ -623,7 +623,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json index e0b9d658835..94dca27149e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json @@ -784,7 +784,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -1859,7 +1861,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json index 7870e51f28d..cc6c3248e07 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json @@ -788,7 +788,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json index 21ab6fb67fd..c716ab3c61b 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json @@ -784,7 +784,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -1859,7 +1861,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json index 1f8c8a0a5bd..6a80a675499 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json @@ -3662,7 +3662,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -6283,7 +6285,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -9136,7 +9140,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -11680,7 +11686,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -14412,7 +14420,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -17065,7 +17075,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -19610,7 +19622,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -20250,7 +20264,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -20900,7 +20916,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -21624,7 +21642,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/path-parameters.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/path-parameters.json new file mode 100644 index 00000000000..8c577603055 --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/path-parameters.json @@ -0,0 +1,2415 @@ +{ + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": { + "originalName": "path-parameters", + "camelCase": { + "unsafeName": "pathParameters", + "safeName": "pathParameters" + }, + "snakeCase": { + "unsafeName": "path_parameters", + "safeName": "path_parameters" + }, + "screamingSnakeCase": { + "unsafeName": "PATH_PARAMETERS", + "safeName": "PATH_PARAMETERS" + }, + "pascalCase": { + "unsafeName": "PathParameters", + "safeName": "PathParameters" + } + }, + "apiDisplayName": null, + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_user:User": { + "inline": false, + "name": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "tags", + "camelCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "snakeCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "screamingSnakeCase": { + "unsafeName": "TAGS", + "safeName": "TAGS" + }, + "pascalCase": { + "unsafeName": "Tags", + "safeName": "Tags" + } + }, + "wireValue": "tags" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_user": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "displayName": null, + "basePath": { + "head": "/user", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {}, + "proto": null + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_user.getOrganization", + "name": { + "originalName": "getOrganization", + "camelCase": { + "unsafeName": "getOrganization", + "safeName": "getOrganization" + }, + "snakeCase": { + "unsafeName": "get_organization", + "safeName": "get_organization" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION", + "safeName": "GET_ORGANIZATION" + }, + "pascalCase": { + "unsafeName": "GetOrganization", + "safeName": "GetOrganization" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/organizations/", + "parts": [ + { + "pathParameter": "organizationId", + "tail": "" + } + ] + }, + "fullPath": { + "head": "/user/organizations/", + "parts": [ + { + "pathParameter": "organizationId", + "tail": "" + } + ] + }, + "pathParameters": [ + { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "allPathParameters": [ + { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "queryParameters": [], + "headers": [], + "requestBody": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "id": "4e3bfbab107e66cc4d7cb058c877bf6a2a5d46aa", + "url": "/user/organizations/organizationId", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "organizationId" + } + } + }, + "jsonExample": "organizationId" + } + } + ], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + } + }, + { + "name": { + "name": { + "originalName": "tags", + "camelCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "snakeCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "screamingSnakeCase": { + "unsafeName": "TAGS", + "safeName": "TAGS" + }, + "pascalCase": { + "unsafeName": "Tags", + "safeName": "Tags" + } + }, + "wireValue": "tags" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tags" + } + } + }, + "jsonExample": "tags" + }, + { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tags" + } + } + }, + "jsonExample": "tags" + } + ], + "itemType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": [ + "tags", + "tags" + ] + } + } + ] + }, + "typeName": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + "jsonExample": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "availability": null, + "docs": null + }, + { + "id": "endpoint_user.getUser", + "name": { + "originalName": "getUser", + "camelCase": { + "unsafeName": "getUser", + "safeName": "getUser" + }, + "snakeCase": { + "unsafeName": "get_user", + "safeName": "get_user" + }, + "screamingSnakeCase": { + "unsafeName": "GET_USER", + "safeName": "GET_USER" + }, + "pascalCase": { + "unsafeName": "GetUser", + "safeName": "GetUser" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/users/", + "parts": [ + { + "pathParameter": "userId", + "tail": "" + } + ] + }, + "fullPath": { + "head": "/user/users/", + "parts": [ + { + "pathParameter": "userId", + "tail": "" + } + ] + }, + "pathParameters": [ + { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "allPathParameters": [ + { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "queryParameters": [], + "headers": [], + "requestBody": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": { + "originalName": "GetUsersRequest", + "camelCase": { + "unsafeName": "getUsersRequest", + "safeName": "getUsersRequest" + }, + "snakeCase": { + "unsafeName": "get_users_request", + "safeName": "get_users_request" + }, + "screamingSnakeCase": { + "unsafeName": "GET_USERS_REQUEST", + "safeName": "GET_USERS_REQUEST" + }, + "pascalCase": { + "unsafeName": "GetUsersRequest", + "safeName": "GetUsersRequest" + } + }, + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + }, + "includePathParameters": true, + "onlyPathParameters": true + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "id": "e6e4a32c0474c2ae67fd7e0137acc9e6caa69c49", + "url": "/user/users/userId", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "userId" + } + } + }, + "jsonExample": "userId" + } + } + ], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + } + }, + { + "name": { + "name": { + "originalName": "tags", + "camelCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "snakeCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "screamingSnakeCase": { + "unsafeName": "TAGS", + "safeName": "TAGS" + }, + "pascalCase": { + "unsafeName": "Tags", + "safeName": "Tags" + } + }, + "wireValue": "tags" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tags" + } + } + }, + "jsonExample": "tags" + }, + { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tags" + } + } + }, + "jsonExample": "tags" + } + ], + "itemType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": [ + "tags", + "tags" + ] + } + } + ] + }, + "typeName": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + "jsonExample": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "availability": null, + "docs": null + }, + { + "id": "endpoint_user.getOrganizationUser", + "name": { + "originalName": "getOrganizationUser", + "camelCase": { + "unsafeName": "getOrganizationUser", + "safeName": "getOrganizationUser" + }, + "snakeCase": { + "unsafeName": "get_organization_user", + "safeName": "get_organization_user" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION_USER", + "safeName": "GET_ORGANIZATION_USER" + }, + "pascalCase": { + "unsafeName": "GetOrganizationUser", + "safeName": "GetOrganizationUser" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "GET", + "basePath": null, + "path": { + "head": "/organizations/", + "parts": [ + { + "pathParameter": "organizationId", + "tail": "/users/" + }, + { + "pathParameter": "userId", + "tail": "" + } + ] + }, + "fullPath": { + "head": "/user/organizations/", + "parts": [ + { + "pathParameter": "organizationId", + "tail": "/users/" + }, + { + "pathParameter": "userId", + "tail": "" + } + ] + }, + "pathParameters": [ + { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + }, + { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "allPathParameters": [ + { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + }, + { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "queryParameters": [], + "headers": [], + "requestBody": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": { + "originalName": "GetOrganizationUserRequest", + "camelCase": { + "unsafeName": "getOrganizationUserRequest", + "safeName": "getOrganizationUserRequest" + }, + "snakeCase": { + "unsafeName": "get_organization_user_request", + "safeName": "get_organization_user_request" + }, + "screamingSnakeCase": { + "unsafeName": "GET_ORGANIZATION_USER_REQUEST", + "safeName": "GET_ORGANIZATION_USER_REQUEST" + }, + "pascalCase": { + "unsafeName": "GetOrganizationUserRequest", + "safeName": "GetOrganizationUserRequest" + } + }, + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + }, + "includePathParameters": true, + "onlyPathParameters": true + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "id": "f4bf06363dd822e7c9689d4314c9d01e4f1bf212", + "url": "/user/organizations/organizationId/users/userId", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "organizationId", + "camelCase": { + "unsafeName": "organizationID", + "safeName": "organizationID" + }, + "snakeCase": { + "unsafeName": "organization_id", + "safeName": "organization_id" + }, + "screamingSnakeCase": { + "unsafeName": "ORGANIZATION_ID", + "safeName": "ORGANIZATION_ID" + }, + "pascalCase": { + "unsafeName": "OrganizationID", + "safeName": "OrganizationID" + } + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "organizationId" + } + } + }, + "jsonExample": "organizationId" + } + }, + { + "name": { + "originalName": "userId", + "camelCase": { + "unsafeName": "userID", + "safeName": "userID" + }, + "snakeCase": { + "unsafeName": "user_id", + "safeName": "user_id" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ID", + "safeName": "USER_ID" + }, + "pascalCase": { + "unsafeName": "UserID", + "safeName": "UserID" + } + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "userId" + } + } + }, + "jsonExample": "userId" + } + } + ], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "name" + } + } + }, + "jsonExample": "name" + } + }, + { + "name": { + "name": { + "originalName": "tags", + "camelCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "snakeCase": { + "unsafeName": "tags", + "safeName": "tags" + }, + "screamingSnakeCase": { + "unsafeName": "TAGS", + "safeName": "TAGS" + }, + "pascalCase": { + "unsafeName": "Tags", + "safeName": "Tags" + } + }, + "wireValue": "tags" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tags" + } + } + }, + "jsonExample": "tags" + }, + { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tags" + } + } + }, + "jsonExample": "tags" + } + ], + "itemType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": [ + "tags", + "tags" + ] + } + } + ] + }, + "typeName": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + "jsonExample": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "availability": null, + "docs": null + } + ] + } + }, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceID", + "safeName": "errorInstanceID" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceID", + "safeName": "ErrorInstanceID" + } + }, + "wireValue": "errorInstanceId" + } + }, + "environments": null, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_user": [ + "type_user:User" + ] + }, + "sharedTypes": [] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "publishConfig": null, + "subpackages": { + "subpackage_user": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "service": "service_user", + "types": [ + "type_user:User" + ], + "errors": [], + "subpackages": [], + "navigationConfig": null, + "webhooks": null, + "websocket": null, + "hasEndpointsInTree": true, + "docs": null + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": null, + "types": [], + "errors": [], + "subpackages": [ + "subpackage_user" + ], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json index b3fcb4a042b..3b6fdb8a339 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json @@ -1399,7 +1399,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json index 4ed08c984da..1de47df5873 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json @@ -514,7 +514,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json index 82b77f8e7d2..af8140cacf7 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json @@ -406,7 +406,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json index 8c4d7ecaa7a..32fc30b9966 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json @@ -406,7 +406,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json index 23872235f6b..66942037c04 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json @@ -605,7 +605,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json index c24dbf6c9bc..94eaf87af08 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json @@ -442,7 +442,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -733,7 +735,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json index 7c5371ae82e..44bba248efc 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json @@ -80583,7 +80583,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -89237,7 +89239,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -97565,7 +97569,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -98930,7 +98936,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -100411,7 +100419,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -118697,7 +118707,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/ts-express-casing.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/ts-express-casing.json index eb5072af951..7dd164a2303 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/ts-express-casing.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/ts-express-casing.json @@ -916,7 +916,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json index bd1d180adce..1c02b764553 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json @@ -1413,7 +1413,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", @@ -2255,7 +2257,9 @@ "unsafeName": "Body", "safeName": "Body" } - } + }, + "includePathParameters": false, + "onlyPathParameters": false }, "requestParameterName": { "originalName": "request", diff --git a/packages/cli/generation/ir-generator/src/converters/convertChannel.ts b/packages/cli/generation/ir-generator/src/converters/convertChannel.ts index 927bbf77970..0a0e7a9e747 100644 --- a/packages/cli/generation/ir-generator/src/converters/convertChannel.ts +++ b/packages/cli/generation/ir-generator/src/converters/convertChannel.ts @@ -30,6 +30,7 @@ import { getOriginalTypeDeclarationForPropertyFromExtensions } from "./type-declarations/convertExampleType"; import { getExtensionsAsList, getPropertyName } from "./type-declarations/convertObjectTypeDeclaration"; +import { getEndpointPathParameters } from "../utils/getEndpointPathParameters"; export async function convertChannel({ channel, @@ -358,9 +359,10 @@ function convertChannelPathParameters({ }; if (example["path-parameters"] != null) { + const rawEndpointPathParameters = getEndpointPathParameters(channel); for (const [key, examplePathParameter] of Object.entries(example["path-parameters"])) { // const rootPathParameterDeclaration = file.rootApiFile["path-parameters"]?.[key]; - const pathParameterDeclaration = channel["path-parameters"]?.[key]; + const pathParameterDeclaration = rawEndpointPathParameters[key]; if (pathParameterDeclaration != null) { pathParameters.push( diff --git a/packages/cli/generation/ir-generator/src/converters/services/convertExampleEndpointCall.ts b/packages/cli/generation/ir-generator/src/converters/services/convertExampleEndpointCall.ts index b1b4f508991..45ce483f73b 100644 --- a/packages/cli/generation/ir-generator/src/converters/services/convertExampleEndpointCall.ts +++ b/packages/cli/generation/ir-generator/src/converters/services/convertExampleEndpointCall.ts @@ -33,6 +33,7 @@ import { getPropertyName } from "../type-declarations/convertObjectTypeDeclarati import { getHeaderName, resolvePathParameterOrThrow } from "./convertHttpService"; import { getQueryParameterName } from "./convertQueryParameter"; import urlJoin from "url-join"; +import { getEndpointPathParameters } from "../../utils/getEndpointPathParameters"; function hashJSON(obj: unknown): string { const jsonString = JSON.stringify(obj); @@ -184,10 +185,11 @@ function convertPathParameters({ }; if (example["path-parameters"] != null) { + const rawEndpointPathParameters = getEndpointPathParameters(endpoint); for (const [key, examplePathParameter] of Object.entries(example["path-parameters"])) { const rootPathParameterDeclaration = file.rootApiFile["path-parameters"]?.[key]; const servicePathParameterDeclaration = service["path-parameters"]?.[key]; - const endpointPathParameterDeclaration = endpoint["path-parameters"]?.[key]; + const endpointPathParameterDeclaration = rawEndpointPathParameters[key]; if (rootPathParameterDeclaration != null) { rootPathParameters.push( diff --git a/packages/cli/generation/ir-generator/src/converters/services/convertHttpSdkRequest.ts b/packages/cli/generation/ir-generator/src/converters/services/convertHttpSdkRequest.ts index abeb3a1fbd3..ae692c2b368 100644 --- a/packages/cli/generation/ir-generator/src/converters/services/convertHttpSdkRequest.ts +++ b/packages/cli/generation/ir-generator/src/converters/services/convertHttpSdkRequest.ts @@ -62,7 +62,9 @@ function convertHttpSdkRequestShape({ } return SdkRequestShape.wrapper({ wrapperName: file.casingsGenerator.generateName(request.name), - bodyKey: file.casingsGenerator.generateName(DEFAULT_BODY_PROPERTY_KEY_IN_WRAPPER) + bodyKey: file.casingsGenerator.generateName(DEFAULT_BODY_PROPERTY_KEY_IN_WRAPPER), + includePathParameters: shouldIncludePathParametersInWrapper(request), + onlyPathParameters: doesRequestHaveOnlyPathParameters({ request, file, typeResolver }) }); }; @@ -129,9 +131,34 @@ export function doesRequestHaveNonBodyProperties({ file: FernFileContext; typeResolver: TypeResolver; }): boolean { - const { headers = {}, "query-parameters": queryParameters = {} } = request; + const { headers = {}, "path-parameters": pathParameters = {}, "query-parameters": queryParameters = {} } = request; - return !areAllHeadersLiteral({ headers, file, typeResolver }) || size(queryParameters) > 0; + return ( + !areAllHeadersLiteral({ headers, file, typeResolver }) || size(pathParameters) > 0 || size(queryParameters) > 0 + ); +} + +function doesRequestHaveOnlyPathParameters({ + request, + file, + typeResolver +}: { + request: RawSchemas.HttpRequestSchema; + file: FernFileContext; + typeResolver: TypeResolver; +}): boolean { + const { headers = {}, "path-parameters": pathParameters = {}, "query-parameters": queryParameters = {} } = request; + + return ( + size(pathParameters) > 0 && + areAllHeadersLiteral({ headers, file, typeResolver }) && + size(queryParameters) === 0 && + request.body == null + ); +} + +function shouldIncludePathParametersInWrapper(request: RawSchemas.HttpRequestSchema): boolean { + return typeof request !== "string" && request?.["path-parameters"] != null; } function areAllHeadersLiteral({ diff --git a/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts b/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts index bc917fe5123..6c3c0549eba 100644 --- a/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts +++ b/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts @@ -33,6 +33,7 @@ import { convertPagination } from "./convertPagination"; import { convertQueryParameter } from "./convertQueryParameter"; import { convertResponseErrors } from "./convertResponseErrors"; import { getTransportForService, getTransportForEndpoint } from "./convertTransport"; +import { getEndpointPathParameters } from "../../utils/getEndpointPathParameters"; export async function convertHttpService({ rootDefaultUrl, @@ -94,7 +95,7 @@ export async function convertHttpService({ endpoints: await Promise.all( Object.entries(serviceDefinition.endpoints).map(async ([endpointKey, endpoint]): Promise => { const endpointPathParameters = await convertPathParameters({ - pathParameters: endpoint["path-parameters"], + pathParameters: getEndpointPathParameters(endpoint), location: PathParameterLocation.Endpoint, file, variableResolver diff --git a/packages/cli/generation/ir-generator/src/index.ts b/packages/cli/generation/ir-generator/src/index.ts index 396cd762320..808b7722343 100644 --- a/packages/cli/generation/ir-generator/src/index.ts +++ b/packages/cli/generation/ir-generator/src/index.ts @@ -36,3 +36,4 @@ export { parseReferenceToTypeName, type ReferenceToTypeName } from "./utils/pars export { IdGenerator } from "./IdGenerator"; export { convertToFernFilepath } from "./utils/convertToFernFilepath"; export { generateEndpointExample } from "./examples/generator/generateSuccessEndpointExample"; +export { getEndpointPathParameters } from "./utils/getEndpointPathParameters"; diff --git a/packages/cli/generation/ir-generator/src/utils/endpointOnlyHasPathParameters.ts b/packages/cli/generation/ir-generator/src/utils/endpointOnlyHasPathParameters.ts new file mode 100644 index 00000000000..19d427e34aa --- /dev/null +++ b/packages/cli/generation/ir-generator/src/utils/endpointOnlyHasPathParameters.ts @@ -0,0 +1,20 @@ +import { RawSchemas } from "@fern-api/fern-definition-schema"; +import { getEndpointPathParameters } from "./getEndpointPathParameters"; + +export function endpointOnlyHasPathParameters( + service: RawSchemas.HttpServiceSchema, + endpoint: RawSchemas.HttpEndpointSchema +): boolean { + const pathParameters = { + ...(service["path-parameters"] ?? {}), + ...getEndpointPathParameters(endpoint) + }; + if (Object.keys(pathParameters).length === 0) { + return false; + } + const request = endpoint.request; + if (request == null || typeof request === "string") { + return false; + } + return request["query-parameters"] == null && request.headers == null && request.body == null; +} diff --git a/packages/cli/generation/ir-generator/src/utils/getEndpointPathParameters.ts b/packages/cli/generation/ir-generator/src/utils/getEndpointPathParameters.ts new file mode 100644 index 00000000000..07e51afcafe --- /dev/null +++ b/packages/cli/generation/ir-generator/src/utils/getEndpointPathParameters.ts @@ -0,0 +1,11 @@ +import { RawSchemas } from "@fern-api/fern-definition-schema"; + +export function getEndpointPathParameters( + endpoint: RawSchemas.HttpEndpointSchema +): Record { + return typeof endpoint.request !== "string" && endpoint.request?.["path-parameters"] + ? endpoint.request?.["path-parameters"] + : endpoint["path-parameters"] != null + ? endpoint["path-parameters"] + : {}; +} diff --git a/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap b/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap index d9ec478dd62..6493115205a 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap +++ b/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap @@ -7750,6 +7750,8 @@ exports[`migrateFromV23ToV22 > migrates extensive 1`] = ` "unsafeName": "body", }, }, + "includePathParameters": false, + "onlyPathParameters": false, "type": "wrapper", "wrapperName": { "camelCase": { @@ -7929,6 +7931,8 @@ exports[`migrateFromV23ToV22 > migrates extensive 1`] = ` "unsafeName": "body", }, }, + "includePathParameters": false, + "onlyPathParameters": false, "type": "wrapper", "wrapperName": { "camelCase": { @@ -8144,6 +8148,8 @@ exports[`migrateFromV23ToV22 > migrates extensive 1`] = ` "unsafeName": "body", }, }, + "includePathParameters": false, + "onlyPathParameters": false, "type": "wrapper", "wrapperName": { "camelCase": { @@ -10387,6 +10393,8 @@ exports[`migrateFromV23ToV22 > migrates extensive 1`] = ` "unsafeName": "body", }, }, + "includePathParameters": false, + "onlyPathParameters": false, "type": "wrapper", "wrapperName": { "camelCase": { @@ -11044,6 +11052,8 @@ exports[`migrateFromV23ToV22 > migrates extensive 1`] = ` "unsafeName": "body", }, }, + "includePathParameters": false, + "onlyPathParameters": false, "type": "wrapper", "wrapperName": { "camelCase": { diff --git a/packages/cli/lazy-fern-workspace/src/OSSWorkspace.ts b/packages/cli/lazy-fern-workspace/src/OSSWorkspace.ts index e1e110ddf26..514cd780450 100644 --- a/packages/cli/lazy-fern-workspace/src/OSSWorkspace.ts +++ b/packages/cli/lazy-fern-workspace/src/OSSWorkspace.ts @@ -67,6 +67,7 @@ export interface SpecImportSettings { objectQueryParameters: boolean; respectReadonlySchemas: boolean; onlyIncludeReferencedSchemas: boolean; + inlinePathParameters: boolean; } export declare namespace OSSWorkspace { @@ -115,6 +116,7 @@ export class OSSWorkspace extends AbstractAPIWorkspace { private respectReadonlySchemas: boolean; private onlyIncludeReferencedSchemas: boolean; + private inlinePathParameters: boolean; constructor({ specs, ...superArgs }: OSSWorkspace.Args) { super(superArgs); @@ -124,6 +126,7 @@ export class OSSWorkspace extends AbstractAPIWorkspace { this.onlyIncludeReferencedSchemas = this.specs.every( (spec) => spec.settings?.onlyIncludeReferencedSchemas ?? false ); + this.inlinePathParameters = this.specs.every((spec) => spec.settings?.inlinePathParameters ?? false); } public async getOpenAPIIr( @@ -183,7 +186,8 @@ export class OSSWorkspace extends AbstractAPIWorkspace { detectGlobalHeaders: settings?.detectGlobalHeaders ?? true, objectQueryParameters, respectReadonlySchemas: this.respectReadonlySchemas, - onlyIncludeReferencedSchemas: this.onlyIncludeReferencedSchemas + onlyIncludeReferencedSchemas: this.onlyIncludeReferencedSchemas, + inlinePathParameters: this.inlinePathParameters }); return { diff --git a/packages/cli/lazy-fern-workspace/src/fern.schema.json b/packages/cli/lazy-fern-workspace/src/fern.schema.json index 1087d676904..349f5983914 100644 --- a/packages/cli/lazy-fern-workspace/src/fern.schema.json +++ b/packages/cli/lazy-fern-workspace/src/fern.schema.json @@ -1874,6 +1874,19 @@ } ] }, + "path-parameters": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/service.HttpPathParameterSchema" + } + }, + { + "type": "null" + } + ] + }, "query-parameters": { "oneOf": [ { diff --git a/packages/cli/lazy-fern-workspace/src/package-yml.schema.json b/packages/cli/lazy-fern-workspace/src/package-yml.schema.json index 13ef45cc541..ea25e26f1c9 100644 --- a/packages/cli/lazy-fern-workspace/src/package-yml.schema.json +++ b/packages/cli/lazy-fern-workspace/src/package-yml.schema.json @@ -1894,6 +1894,19 @@ } ] }, + "path-parameters": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/service.HttpPathParameterSchema" + } + }, + { + "type": "null" + } + ] + }, "query-parameters": { "oneOf": [ { diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/path-parameters.json b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/path-parameters.json new file mode 100644 index 00000000000..6e3e29236bc --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/path-parameters.json @@ -0,0 +1,317 @@ +{ + "types": { + "type_user:User": { + "name": "User", + "shape": { + "type": "object", + "extends": [], + "properties": [ + { + "key": "name", + "valueType": { + "type": "primitive", + "value": { + "type": "string" + } + } + }, + { + "key": "tags", + "valueType": { + "type": "list", + "itemType": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + } + ], + "extraProperties": { + "type": "unknown" + } + } + } + }, + "subpackages": { + "subpackage_user": { + "subpackageId": "subpackage_user", + "name": "user", + "endpoints": [ + { + "auth": false, + "method": "GET", + "id": "getOrganization", + "originalEndpointId": "endpoint_user.getOrganization", + "name": "Get Organization", + "path": { + "pathParameters": [ + { + "key": "organizationId", + "type": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + ], + "parts": [ + { + "type": "literal", + "value": "/user" + }, + { + "type": "literal", + "value": "/organizations/" + }, + { + "type": "pathParameter", + "value": "organizationId" + }, + { + "type": "literal", + "value": "" + } + ] + }, + "queryParameters": [], + "headers": [], + "response": { + "type": { + "type": "reference", + "value": { + "type": "id", + "value": "type_user:User" + } + } + }, + "errorsV2": [], + "examples": [ + { + "path": "/user/organizations/organizationId", + "pathParameters": { + "organizationId": "organizationId" + }, + "queryParameters": {}, + "headers": {}, + "responseStatusCode": 200, + "responseBody": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + }, + "responseBodyV3": { + "type": "json", + "value": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + }, + "codeSamples": [] + } + ] + }, + { + "auth": false, + "method": "GET", + "id": "getUser", + "originalEndpointId": "endpoint_user.getUser", + "name": "Get User", + "path": { + "pathParameters": [ + { + "key": "userId", + "type": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + ], + "parts": [ + { + "type": "literal", + "value": "/user" + }, + { + "type": "literal", + "value": "/users/" + }, + { + "type": "pathParameter", + "value": "userId" + }, + { + "type": "literal", + "value": "" + } + ] + }, + "queryParameters": [], + "headers": [], + "response": { + "type": { + "type": "reference", + "value": { + "type": "id", + "value": "type_user:User" + } + } + }, + "errorsV2": [], + "examples": [ + { + "path": "/user/users/userId", + "pathParameters": { + "userId": "userId" + }, + "queryParameters": {}, + "headers": {}, + "responseStatusCode": 200, + "responseBody": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + }, + "responseBodyV3": { + "type": "json", + "value": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + }, + "codeSamples": [] + } + ] + }, + { + "auth": false, + "method": "GET", + "id": "getOrganizationUser", + "originalEndpointId": "endpoint_user.getOrganizationUser", + "name": "Get Organization User", + "path": { + "pathParameters": [ + { + "key": "organizationId", + "type": { + "type": "primitive", + "value": { + "type": "string" + } + } + }, + { + "key": "userId", + "type": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + ], + "parts": [ + { + "type": "literal", + "value": "/user" + }, + { + "type": "literal", + "value": "/organizations/" + }, + { + "type": "pathParameter", + "value": "organizationId" + }, + { + "type": "literal", + "value": "/users/" + }, + { + "type": "pathParameter", + "value": "userId" + }, + { + "type": "literal", + "value": "" + } + ] + }, + "queryParameters": [], + "headers": [], + "response": { + "type": { + "type": "reference", + "value": { + "type": "id", + "value": "type_user:User" + } + } + }, + "errorsV2": [], + "examples": [ + { + "path": "/user/organizations/organizationId/users/userId", + "pathParameters": { + "organizationId": "organizationId", + "userId": "userId" + }, + "queryParameters": {}, + "headers": {}, + "responseStatusCode": 200, + "responseBody": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + }, + "responseBodyV3": { + "type": "json", + "value": { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + }, + "codeSamples": [] + } + ] + } + ], + "webhooks": [], + "websockets": [], + "types": [ + "type_user:User" + ], + "subpackages": [] + } + }, + "rootPackage": { + "endpoints": [], + "webhooks": [], + "websockets": [], + "types": [], + "subpackages": [ + "subpackage_user" + ] + }, + "snippetsConfiguration": {}, + "globalHeaders": [] +} \ No newline at end of file diff --git a/packages/cli/workspace-loader/src/loadAPIWorkspace.ts b/packages/cli/workspace-loader/src/loadAPIWorkspace.ts index 410baab85d5..bd58441f52c 100644 --- a/packages/cli/workspace-loader/src/loadAPIWorkspace.ts +++ b/packages/cli/workspace-loader/src/loadAPIWorkspace.ts @@ -73,7 +73,8 @@ export async function loadSingleNamespaceAPIWorkspace({ cooerceEnumsToLiterals: definition.settings?.coerceEnumsToLiterals ?? true, objectQueryParameters: definition.settings?.objectQueryParameters ?? false, respectReadonlySchemas: definition.settings?.respectReadonlySchemas ?? false, - onlyIncludeReferencedSchemas: definition.settings?.onlyIncludeReferencedSchemas ?? false + onlyIncludeReferencedSchemas: definition.settings?.onlyIncludeReferencedSchemas ?? false, + inlinePathParameters: definition.settings?.inlinePathParameters ?? false } }); continue; @@ -117,7 +118,8 @@ export async function loadSingleNamespaceAPIWorkspace({ cooerceEnumsToLiterals: definition.settings?.coerceEnumsToLiterals ?? true, objectQueryParameters: definition.settings?.objectQueryParameters ?? false, respectReadonlySchemas: definition.settings?.respectReadonlySchemas ?? false, - onlyIncludeReferencedSchemas: definition.settings?.onlyIncludeReferencedSchemas ?? false + onlyIncludeReferencedSchemas: definition.settings?.onlyIncludeReferencedSchemas ?? false, + inlinePathParameters: definition.settings?.inlinePathParameters ?? false }, source: { type: "openapi", diff --git a/packages/cli/workspace-loader/src/types/Workspace.ts b/packages/cli/workspace-loader/src/types/Workspace.ts index f373035862f..ece058bd8eb 100644 --- a/packages/cli/workspace-loader/src/types/Workspace.ts +++ b/packages/cli/workspace-loader/src/types/Workspace.ts @@ -69,6 +69,7 @@ export interface SpecImportSettings { objectQueryParameters: boolean; respectReadonlySchemas: boolean; onlyIncludeReferencedSchemas: boolean; + inlinePathParameters: boolean; } export interface OpenAPIFile { diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/VERSION b/packages/ir-sdk/fern/apis/ir-types-latest/VERSION index 1fbbd0efb72..2ab5d680d0d 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/VERSION +++ b/packages/ir-sdk/fern/apis/ir-types-latest/VERSION @@ -1 +1 @@ -53.19.0 +53.20.0 diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md b/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md index 7b8f45015cf..5d648b459f2 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md +++ b/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v53.20.0] - 2024-11-04 + +- Internal: Add `includePathParameters` and `onlyPathParameters` property to the wrapped request. + + With this, the generator can determine whether or not the path parameters should be included in + the wrapped request, or if the wrapped request can be omitted entirely. + ## [v53.19.0] - 2024-11-04 - Internal: Add errors property to dynamic `EndpointSnippetResponse`. diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml b/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml index e3534ebab7f..01185fd04ab 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml +++ b/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml @@ -181,6 +181,8 @@ types: properties: wrapperName: commons.Name bodyKey: commons.Name + includePathParameters: optional + onlyPathParameters: optional HttpResponse: properties: diff --git a/packages/ir-sdk/src/sdk/api/resources/http/types/SdkRequestWrapper.ts b/packages/ir-sdk/src/sdk/api/resources/http/types/SdkRequestWrapper.ts index c6c3ecdca0c..4c51b79db60 100644 --- a/packages/ir-sdk/src/sdk/api/resources/http/types/SdkRequestWrapper.ts +++ b/packages/ir-sdk/src/sdk/api/resources/http/types/SdkRequestWrapper.ts @@ -7,4 +7,6 @@ import * as FernIr from "../../../index"; export interface SdkRequestWrapper { wrapperName: FernIr.Name; bodyKey: FernIr.Name; + includePathParameters: boolean | undefined; + onlyPathParameters: boolean | undefined; } diff --git a/packages/ir-sdk/src/sdk/serialization/resources/http/types/SdkRequestWrapper.ts b/packages/ir-sdk/src/sdk/serialization/resources/http/types/SdkRequestWrapper.ts index 703ad42abb3..727682f72fa 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/http/types/SdkRequestWrapper.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/http/types/SdkRequestWrapper.ts @@ -13,11 +13,15 @@ export const SdkRequestWrapper: core.serialization.ObjectSchema< > = core.serialization.objectWithoutOptionalProperties({ wrapperName: Name, bodyKey: Name, + includePathParameters: core.serialization.boolean().optional(), + onlyPathParameters: core.serialization.boolean().optional(), }); export declare namespace SdkRequestWrapper { interface Raw { wrapperName: Name.Raw; bodyKey: Name.Raw; + includePathParameters?: boolean | null; + onlyPathParameters?: boolean | null; } } diff --git a/seed/csharp-model/path-parameters/.github/workflows/ci.yml b/seed/csharp-model/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..b34b75c042c --- /dev/null +++ b/seed/csharp-model/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Build Release + run: dotnet build src -c Release /p:ContinuousIntegrationBuild=true + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Run Tests + run: | + dotnet test src + + + publish: + needs: [compile] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Publish + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: | + dotnet pack src -c Release + dotnet nuget push src/SeedPathParameters/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" diff --git a/seed/csharp-model/path-parameters/.gitignore b/seed/csharp-model/path-parameters/.gitignore new file mode 100644 index 00000000000..11014f2b33d --- /dev/null +++ b/seed/csharp-model/path-parameters/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +## This is based on `dotnet new gitignore` and customized by Fern + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +# [Rr]elease/ (Ignored by Fern) +# [Rr]eleases/ (Ignored by Fern) +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +# [Ll]og/ (Ignored by Fern) +# [Ll]ogs/ (Ignored by Fern) + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/seed/csharp-model/path-parameters/.mock/definition/api.yml b/seed/csharp-model/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/csharp-model/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/csharp-model/path-parameters/.mock/definition/user.yml b/seed/csharp-model/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/csharp-model/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/csharp-model/path-parameters/.mock/fern.config.json b/seed/csharp-model/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/csharp-model/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/csharp-model/path-parameters/.mock/generators.yml b/seed/csharp-model/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/csharp-model/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/csharp-model/path-parameters/snippet-templates.json b/seed/csharp-model/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/csharp-model/path-parameters/snippet.json b/seed/csharp-model/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/Core/EnumSerializerTests.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/Core/EnumSerializerTests.cs new file mode 100644 index 00000000000..548110aa63e --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/Core/EnumSerializerTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; +using NUnit.Framework; +using SeedPathParameters.Core; + +namespace SeedPathParameters.Test.Core +{ + [TestFixture] + public class StringEnumSerializerTests + { + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; + + private const DummyEnum KnownEnumValue2 = DummyEnum.KnownValue2; + private const string KnownEnumValue2String = "known_value2"; + + private static readonly string JsonWithKnownEnum2 = $$""" + { + "enum_property": "{{KnownEnumValue2String}}" + } + """; + + [Test] + public void ShouldParseKnownEnumValue2() + { + var obj = JsonSerializer.Deserialize(JsonWithKnownEnum2, JsonOptions); + Assert.That(obj, Is.Not.Null); + Assert.That(obj.EnumProperty, Is.EqualTo(KnownEnumValue2)); + } + + [Test] + public void ShouldSerializeKnownEnumValue2() + { + var json = JsonSerializer.SerializeToElement( + new DummyObject { EnumProperty = KnownEnumValue2 }, + JsonOptions + ); + TestContext.WriteLine("Serialized JSON: \n" + json); + var enumString = json.GetProperty("enum_property").GetString(); + Assert.That(enumString, Is.Not.Null); + Assert.That(enumString, Is.EqualTo(KnownEnumValue2String)); + } + } + + public class DummyObject + { + [JsonPropertyName("enum_property")] + public DummyEnum EnumProperty { get; set; } + } + + [JsonConverter(typeof(EnumSerializer))] + public enum DummyEnum + { + [EnumMember(Value = "known_value1")] + KnownValue1, + + [EnumMember(Value = "known_value2")] + KnownValue2, + } +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.Custom.props b/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.Custom.props new file mode 100644 index 00000000000..55e683b0772 --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.Custom.props @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.csproj b/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.csproj new file mode 100644 index 00000000000..26100094aee --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/CollectionItemSerializer.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/CollectionItemSerializer.cs new file mode 100644 index 00000000000..74ac511a039 --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +internal class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter +{ + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/Constants.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/Constants.cs new file mode 100644 index 00000000000..0c14456a8af --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace SeedPathParameters.Core; + +internal static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; + public const string DateFormat = "yyyy-MM-dd"; +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/DateTimeSerializer.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/DateTimeSerializer.cs new file mode 100644 index 00000000000..f596a61ef85 --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/DateTimeSerializer.cs @@ -0,0 +1,22 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +internal class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/EnumSerializer.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/EnumSerializer.cs new file mode 100644 index 00000000000..e1cd84fd1cb --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/EnumSerializer.cs @@ -0,0 +1,53 @@ +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +internal class EnumSerializer : JsonConverter + where TEnum : struct, System.Enum +{ + private readonly Dictionary _enumToString = new(); + private readonly Dictionary _stringToEnum = new(); + + public EnumSerializer() + { + var type = typeof(TEnum); + var values = Enum.GetValues(type); + + foreach (var value in values) + { + var enumValue = (TEnum)value; + var enumMember = type.GetMember(enumValue.ToString())[0]; + var attr = enumMember + .GetCustomAttributes(typeof(EnumMemberAttribute), false) + .Cast() + .FirstOrDefault(); + + var stringValue = + attr?.Value + ?? value.ToString() + ?? throw new Exception("Unexpected null enum toString value"); + + _enumToString.Add(enumValue, stringValue); + _stringToEnum.Add(stringValue, enumValue); + } + } + + public override TEnum Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new Exception("The JSON value could not be read as a string."); + return _stringToEnum.TryGetValue(stringValue, out var enumValue) ? enumValue : default; + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(_enumToString[value]); + } +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/JsonConfiguration.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/JsonConfiguration.cs new file mode 100644 index 00000000000..d4a63e77218 --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/JsonConfiguration.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +internal static partial class JsonOptions +{ + public static readonly JsonSerializerOptions JsonSerializerOptions; + + static JsonOptions() + { + var options = new JsonSerializerOptions + { + Converters = { new DateTimeSerializer(), new OneOfSerializer() }, + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; + } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); +} + +internal static class JsonUtils +{ + public static string Serialize(T obj) + { + return JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + } + + public static T Deserialize(string json) + { + return JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; + } +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/OneOfSerializer.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/OneOfSerializer.cs new file mode 100644 index 00000000000..70815fdd4f1 --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/OneOfSerializer.cs @@ -0,0 +1,69 @@ +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using OneOf; + +namespace SeedPathParameters.Core; + +internal class OneOfSerializer : JsonConverter +{ + public override IOneOf? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (IOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + private static (System.Type type, MethodInfo cast)[] GetOneOfTypes(System.Type typeToConvert) + { + var casts = typeToConvert + .GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + var type = typeToConvert; + while (type != null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + return type.GetGenericArguments() + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); + } +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/Public/Version.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/Public/Version.cs new file mode 100644 index 00000000000..82ce383c0e7 --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/Core/Public/Version.cs @@ -0,0 +1,6 @@ +namespace SeedPathParameters; + +internal class Version +{ + public const string Current = "0.0.1"; +} diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/SeedPathParameters.Custom.props b/seed/csharp-model/path-parameters/src/SeedPathParameters/SeedPathParameters.Custom.props new file mode 100644 index 00000000000..70df2849401 --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/SeedPathParameters.Custom.props @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/SeedPathParameters.csproj b/seed/csharp-model/path-parameters/src/SeedPathParameters/SeedPathParameters.csproj new file mode 100644 index 00000000000..ec82eb5bc3f --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/SeedPathParameters.csproj @@ -0,0 +1,53 @@ + + + + + net462;net8.0;net7.0;net6.0;netstandard2.0 + enable + false + 12 + enable + 0.0.1 + $(Version) + $(Version) + README.md + https://github.com/path-parameters/fern + + + + true + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + <_Parameter1>SeedPathParameters.Test + + + + + diff --git a/seed/csharp-model/path-parameters/src/SeedPathParameters/User/User.cs b/seed/csharp-model/path-parameters/src/SeedPathParameters/User/User.cs new file mode 100644 index 00000000000..b8777f833fa --- /dev/null +++ b/seed/csharp-model/path-parameters/src/SeedPathParameters/User/User.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public record User +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("tags")] + public IEnumerable Tags { get; set; } = new List(); + + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/path-parameters/.github/workflows/ci.yml b/seed/csharp-sdk/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..b34b75c042c --- /dev/null +++ b/seed/csharp-sdk/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Build Release + run: dotnet build src -c Release /p:ContinuousIntegrationBuild=true + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Run Tests + run: | + dotnet test src + + + publish: + needs: [compile] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Publish + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: | + dotnet pack src -c Release + dotnet nuget push src/SeedPathParameters/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" diff --git a/seed/csharp-sdk/path-parameters/.gitignore b/seed/csharp-sdk/path-parameters/.gitignore new file mode 100644 index 00000000000..11014f2b33d --- /dev/null +++ b/seed/csharp-sdk/path-parameters/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +## This is based on `dotnet new gitignore` and customized by Fern + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +# [Rr]elease/ (Ignored by Fern) +# [Rr]eleases/ (Ignored by Fern) +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +# [Ll]og/ (Ignored by Fern) +# [Ll]ogs/ (Ignored by Fern) + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/seed/csharp-sdk/path-parameters/.mock/definition/api.yml b/seed/csharp-sdk/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/csharp-sdk/path-parameters/.mock/definition/user.yml b/seed/csharp-sdk/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/csharp-sdk/path-parameters/.mock/fern.config.json b/seed/csharp-sdk/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/csharp-sdk/path-parameters/.mock/generators.yml b/seed/csharp-sdk/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/csharp-sdk/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/csharp-sdk/path-parameters/README.md b/seed/csharp-sdk/path-parameters/README.md new file mode 100644 index 00000000000..530e33c3731 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/README.md @@ -0,0 +1,87 @@ +# Seed C# Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FC%23) +[![nuget shield](https://img.shields.io/nuget/v/SeedPathParameters)](https://nuget.org/packages/SeedPathParameters) + +The Seed C# library provides convenient access to the Seed API from C#. + +## Installation + +```sh +nuget install SeedPathParameters +``` + +## Usage + +Instantiate and use the client with the following: + +```csharp +using SeedPathParameters; + +var client = new SeedPathParametersClient(); +await client.User.GetOrganizationAsync("organizationId"); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```csharp +using SeedPathParameters; + +try { + var response = await client.User.GetOrganizationAsync(...); +} catch (SeedPathParametersApiException e) { + System.Console.WriteLine(e.Body); + System.Console.WriteLine(e.StatusCode); +} +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `MaxRetries` request option to configure this behavior. + +```csharp +var response = await client.User.GetOrganizationAsync( + ..., + new RequestOptions { + MaxRetries: 0 // Override MaxRetries at the request level + } +); +``` + +### Timeouts + +The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior. + +```csharp +var response = await client.User.GetOrganizationAsync( + ..., + new RequestOptions { + Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s + } +); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! \ No newline at end of file diff --git a/seed/csharp-sdk/path-parameters/reference.md b/seed/csharp-sdk/path-parameters/reference.md new file mode 100644 index 00000000000..f99df382723 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/reference.md @@ -0,0 +1,149 @@ +# Reference +## User +
client.User.GetOrganizationAsync(organizationId) -> User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.User.GetOrganizationAsync("organizationId"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**organizationId:** `string` + +
+
+
+
+ + +
+
+
+ +
client.User.GetUserAsync(userId, GetUsersRequest { ... }) -> User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.User.GetUserAsync("userId", new GetUsersRequest()); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**userId:** `string` + +
+
+ +
+
+ +**request:** `GetUsersRequest` + +
+
+
+
+ + +
+
+
+ +
client.User.GetOrganizationUserAsync(organizationId, userId, GetOrganizationUserRequest { ... }) -> User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.User.GetOrganizationUserAsync( + "organizationId", + "userId", + new GetOrganizationUserRequest() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**organizationId:** `string` + +
+
+ +
+
+ +**userId:** `string` + +
+
+ +
+
+ +**request:** `GetOrganizationUserRequest` + +
+
+
+
+ + +
+
+
diff --git a/seed/csharp-sdk/path-parameters/snippet-templates.json b/seed/csharp-sdk/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/csharp-sdk/path-parameters/snippet.json b/seed/csharp-sdk/path-parameters/snippet.json new file mode 100644 index 00000000000..5191be0218e --- /dev/null +++ b/seed/csharp-sdk/path-parameters/snippet.json @@ -0,0 +1,41 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": null, + "id": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganization" + }, + "snippet": { + "type": "typescript", + "client": "using SeedPathParameters;\n\nvar client = new SeedPathParametersClient();\nawait client.User.GetOrganizationAsync(\"organizationId\");\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/user/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getUser" + }, + "snippet": { + "type": "typescript", + "client": "using SeedPathParameters;\n\nvar client = new SeedPathParametersClient();\nawait client.User.GetUserAsync(\"userId\", new GetUsersRequest());\n" + } + }, + { + "example_identifier": null, + "id": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganizationUser" + }, + "snippet": { + "type": "typescript", + "client": "using SeedPathParameters;\n\nvar client = new SeedPathParametersClient();\nawait client.User.GetOrganizationUserAsync(\n \"organizationId\",\n \"userId\",\n new GetOrganizationUserRequest()\n);\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Core/EnumSerializerTests.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Core/EnumSerializerTests.cs new file mode 100644 index 00000000000..548110aa63e --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Core/EnumSerializerTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; +using NUnit.Framework; +using SeedPathParameters.Core; + +namespace SeedPathParameters.Test.Core +{ + [TestFixture] + public class StringEnumSerializerTests + { + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; + + private const DummyEnum KnownEnumValue2 = DummyEnum.KnownValue2; + private const string KnownEnumValue2String = "known_value2"; + + private static readonly string JsonWithKnownEnum2 = $$""" + { + "enum_property": "{{KnownEnumValue2String}}" + } + """; + + [Test] + public void ShouldParseKnownEnumValue2() + { + var obj = JsonSerializer.Deserialize(JsonWithKnownEnum2, JsonOptions); + Assert.That(obj, Is.Not.Null); + Assert.That(obj.EnumProperty, Is.EqualTo(KnownEnumValue2)); + } + + [Test] + public void ShouldSerializeKnownEnumValue2() + { + var json = JsonSerializer.SerializeToElement( + new DummyObject { EnumProperty = KnownEnumValue2 }, + JsonOptions + ); + TestContext.WriteLine("Serialized JSON: \n" + json); + var enumString = json.GetProperty("enum_property").GetString(); + Assert.That(enumString, Is.Not.Null); + Assert.That(enumString, Is.EqualTo(KnownEnumValue2String)); + } + } + + public class DummyObject + { + [JsonPropertyName("enum_property")] + public DummyEnum EnumProperty { get; set; } + } + + [JsonConverter(typeof(EnumSerializer))] + public enum DummyEnum + { + [EnumMember(Value = "known_value1")] + KnownValue1, + + [EnumMember(Value = "known_value2")] + KnownValue2, + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Core/RawClientTests.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Core/RawClientTests.cs new file mode 100644 index 00000000000..54e8dfd53a3 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Core/RawClientTests.cs @@ -0,0 +1,113 @@ +using System; +using System.Net.Http; +using FluentAssertions; +using NUnit.Framework; +using SeedPathParameters.Core; +using WireMock.Server; +using SystemTask = System.Threading.Tasks.Task; +using WireMockRequest = WireMock.RequestBuilders.Request; +using WireMockResponse = WireMock.ResponseBuilders.Response; + +namespace SeedPathParameters.Test.Core +{ + [TestFixture] + public class RawClientTests + { + private WireMockServer _server; + private HttpClient _httpClient; + private RawClient _rawClient; + private string _baseUrl; + private const int _maxRetries = 3; + + [SetUp] + public void SetUp() + { + _server = WireMockServer.Start(); + _baseUrl = _server.Url ?? ""; + _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) }; + _rawClient = new RawClient( + new ClientOptions() { HttpClient = _httpClient, MaxRetries = _maxRetries } + ); + } + + [Test] + [TestCase(408)] + [TestCase(429)] + [TestCase(500)] + [TestCase(504)] + public async SystemTask MakeRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Server Error") + .WillSetStateTo("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode)); + + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WhenStateIs("Success") + .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success")); + + var request = new RawClient.BaseApiRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.MakeRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(200)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.That(content, Is.EqualTo("Success")); + + Assert.That(_server.LogEntries.Count, Is.EqualTo(_maxRetries)); + } + + [Test] + [TestCase(400)] + [TestCase(409)] + public async SystemTask MakeRequestAsync_ShouldRetry_OnNonRetryableStatusCodes( + int statusCode + ) + { + _server + .Given(WireMockRequest.Create().WithPath("/test").UsingGet()) + .InScenario("Retry") + .WillSetStateTo("Server Error") + .RespondWith( + WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure") + ); + + var request = new RawClient.BaseApiRequest + { + BaseUrl = _baseUrl, + Method = HttpMethod.Get, + Path = "/test", + }; + + var response = await _rawClient.MakeRequestAsync(request); + Assert.That(response.StatusCode, Is.EqualTo(statusCode)); + + var content = await response.Raw.Content.ReadAsStringAsync(); + Assert.That(content, Is.EqualTo("Failure")); + + Assert.That(_server.LogEntries.Count, Is.EqualTo(1)); + } + + [TearDown] + public void TearDown() + { + _server.Dispose(); + _httpClient.Dispose(); + } + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.Custom.props b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.Custom.props new file mode 100644 index 00000000000..55e683b0772 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.Custom.props @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.csproj b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.csproj new file mode 100644 index 00000000000..26100094aee --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/SeedPathParameters.Test.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/TestClient.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/TestClient.cs new file mode 100644 index 00000000000..800c766b396 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/TestClient.cs @@ -0,0 +1,8 @@ +using NUnit.Framework; + +#nullable enable + +namespace SeedPathParameters.Test; + +[TestFixture] +public class TestClient { } diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/BaseMockServerTest.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/BaseMockServerTest.cs new file mode 100644 index 00000000000..097c5cf7e28 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/BaseMockServerTest.cs @@ -0,0 +1,39 @@ +using NUnit.Framework; +using SeedPathParameters; +using WireMock.Logging; +using WireMock.Server; +using WireMock.Settings; + +#nullable enable + +namespace SeedPathParameters.Test.Unit.MockServer; + +[SetUpFixture] +public class BaseMockServerTest +{ + protected static WireMockServer Server { get; set; } = null!; + + protected static SeedPathParametersClient Client { get; set; } = null!; + + protected static RequestOptions RequestOptions { get; set; } = null!; + + [OneTimeSetUp] + public void GlobalSetup() + { + // Start the WireMock server + Server = WireMockServer.Start( + new WireMockServerSettings { Logger = new WireMockConsoleLogger() } + ); + + // Initialize the Client + Client = new SeedPathParametersClient(); + + RequestOptions = new RequestOptions { BaseUrl = Server.Urls[0] }; + } + + [OneTimeTearDown] + public void GlobalTeardown() + { + Server.Stop(); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetOrganizationTest.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetOrganizationTest.cs new file mode 100644 index 00000000000..a6fae330c5d --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetOrganizationTest.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using FluentAssertions.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters.Test.Unit.MockServer; + +[TestFixture] +public class GetOrganizationTest : BaseMockServerTest +{ + [Test] + public async Task MockServerTest() + { + const string mockResponse = """ + { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/user/organizations/organizationId") + .UsingGet() + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.User.GetOrganizationAsync("organizationId", RequestOptions); + JToken + .Parse(mockResponse) + .Should() + .BeEquivalentTo(JToken.Parse(JsonUtils.Serialize(response))); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetOrganizationUserTest.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetOrganizationUserTest.cs new file mode 100644 index 00000000000..bff7ff9a6e3 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetOrganizationUserTest.cs @@ -0,0 +1,53 @@ +using System.Threading.Tasks; +using FluentAssertions.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using SeedPathParameters; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters.Test.Unit.MockServer; + +[TestFixture] +public class GetOrganizationUserTest : BaseMockServerTest +{ + [Test] + public async Task MockServerTest() + { + const string mockResponse = """ + { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + """; + + Server + .Given( + WireMock + .RequestBuilders.Request.Create() + .WithPath("/user/organizations/organizationId/users/userId") + .UsingGet() + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.User.GetOrganizationUserAsync( + "organizationId", + "userId", + new GetOrganizationUserRequest(), + RequestOptions + ); + JToken + .Parse(mockResponse) + .Should() + .BeEquivalentTo(JToken.Parse(JsonUtils.Serialize(response))); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetUserTest.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetUserTest.cs new file mode 100644 index 00000000000..82d8f5b57ec --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters.Test/Unit/MockServer/GetUserTest.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using FluentAssertions.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using SeedPathParameters; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters.Test.Unit.MockServer; + +[TestFixture] +public class GetUserTest : BaseMockServerTest +{ + [Test] + public async Task MockServerTest() + { + const string mockResponse = """ + { + "name": "name", + "tags": [ + "tags", + "tags" + ] + } + """; + + Server + .Given( + WireMock.RequestBuilders.Request.Create().WithPath("/user/users/userId").UsingGet() + ) + .RespondWith( + WireMock + .ResponseBuilders.Response.Create() + .WithStatusCode(200) + .WithBody(mockResponse) + ); + + var response = await Client.User.GetUserAsync( + "userId", + new GetUsersRequest(), + RequestOptions + ); + JToken + .Parse(mockResponse) + .Should() + .BeEquivalentTo(JToken.Parse(JsonUtils.Serialize(response))); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/CollectionItemSerializer.cs new file mode 100644 index 00000000000..74ac511a039 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +internal class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter +{ + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Constants.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Constants.cs new file mode 100644 index 00000000000..0c14456a8af --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace SeedPathParameters.Core; + +internal static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; + public const string DateFormat = "yyyy-MM-dd"; +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/DateTimeSerializer.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/DateTimeSerializer.cs new file mode 100644 index 00000000000..f596a61ef85 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/DateTimeSerializer.cs @@ -0,0 +1,22 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +internal class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/EnumSerializer.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/EnumSerializer.cs new file mode 100644 index 00000000000..e1cd84fd1cb --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/EnumSerializer.cs @@ -0,0 +1,53 @@ +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +internal class EnumSerializer : JsonConverter + where TEnum : struct, System.Enum +{ + private readonly Dictionary _enumToString = new(); + private readonly Dictionary _stringToEnum = new(); + + public EnumSerializer() + { + var type = typeof(TEnum); + var values = Enum.GetValues(type); + + foreach (var value in values) + { + var enumValue = (TEnum)value; + var enumMember = type.GetMember(enumValue.ToString())[0]; + var attr = enumMember + .GetCustomAttributes(typeof(EnumMemberAttribute), false) + .Cast() + .FirstOrDefault(); + + var stringValue = + attr?.Value + ?? value.ToString() + ?? throw new Exception("Unexpected null enum toString value"); + + _enumToString.Add(enumValue, stringValue); + _stringToEnum.Add(stringValue, enumValue); + } + } + + public override TEnum Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new Exception("The JSON value could not be read as a string."); + return _stringToEnum.TryGetValue(stringValue, out var enumValue) ? enumValue : default; + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(_enumToString[value]); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Extensions.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Extensions.cs new file mode 100644 index 00000000000..4d1dc541820 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Extensions.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace SeedPathParameters.Core; + +internal static class Extensions +{ + public static string Stringify(this Enum value) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = (EnumMemberAttribute) + Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); + return attribute?.Value ?? value.ToString(); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/HeaderValue.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/HeaderValue.cs new file mode 100644 index 00000000000..a8ecd710806 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/HeaderValue.cs @@ -0,0 +1,17 @@ +using OneOf; + +namespace SeedPathParameters.Core; + +internal sealed class HeaderValue(OneOf> value) + : OneOfBase>(value) +{ + public static implicit operator HeaderValue(string value) + { + return new HeaderValue(value); + } + + public static implicit operator HeaderValue(Func value) + { + return new HeaderValue(value); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Headers.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Headers.cs new file mode 100644 index 00000000000..ed96fa26787 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Headers.cs @@ -0,0 +1,17 @@ +namespace SeedPathParameters.Core; + +internal sealed class Headers : Dictionary +{ + public Headers() { } + + public Headers(Dictionary value) + { + foreach (var kvp in value) + { + this[kvp.Key] = new HeaderValue(kvp.Value); + } + } + + public Headers(IEnumerable> value) + : base(value.ToDictionary(e => e.Key, e => e.Value)) { } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/HttpMethodExtensions.cs new file mode 100644 index 00000000000..b65c14f1765 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/HttpMethodExtensions.cs @@ -0,0 +1,8 @@ +using System.Net.Http; + +namespace SeedPathParameters.Core; + +internal static class HttpMethodExtensions +{ + public static readonly HttpMethod Patch = new("PATCH"); +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/IRequestOptions.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/IRequestOptions.cs new file mode 100644 index 00000000000..b0d41b09a13 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/IRequestOptions.cs @@ -0,0 +1,34 @@ +using System; +using System.Net.Http; + +#nullable enable + +namespace SeedPathParameters.Core; + +internal interface IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; init; } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; init; } + + /// + /// The http headers sent with the request. + /// + internal Headers Headers { get; init; } + + /// + /// The http client used to make requests. + /// + public int? MaxRetries { get; init; } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; init; } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/JsonConfiguration.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/JsonConfiguration.cs new file mode 100644 index 00000000000..d4a63e77218 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/JsonConfiguration.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedPathParameters.Core; + +internal static partial class JsonOptions +{ + public static readonly JsonSerializerOptions JsonSerializerOptions; + + static JsonOptions() + { + var options = new JsonSerializerOptions + { + Converters = { new DateTimeSerializer(), new OneOfSerializer() }, + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; + } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); +} + +internal static class JsonUtils +{ + public static string Serialize(T obj) + { + return JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + } + + public static T Deserialize(string json) + { + return JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/OneOfSerializer.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/OneOfSerializer.cs new file mode 100644 index 00000000000..70815fdd4f1 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/OneOfSerializer.cs @@ -0,0 +1,69 @@ +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using OneOf; + +namespace SeedPathParameters.Core; + +internal class OneOfSerializer : JsonConverter +{ + public override IOneOf? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (IOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + private static (System.Type type, MethodInfo cast)[] GetOneOfTypes(System.Type typeToConvert) + { + var casts = typeToConvert + .GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + var type = typeToConvert; + while (type != null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + return type.GetGenericArguments() + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/ClientOptions.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/ClientOptions.cs new file mode 100644 index 00000000000..afdd97e3896 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/ClientOptions.cs @@ -0,0 +1,50 @@ +using System; +using System.Net.Http; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public partial class ClientOptions +{ + /// + /// The Base URL for the API. + /// + public string BaseUrl { get; init; } = ""; + + /// + /// The http client used to make requests. + /// + public HttpClient HttpClient { get; init; } = new HttpClient(); + + /// + /// The http client used to make requests. + /// + public int MaxRetries { get; init; } = 2; + + /// + /// The timeout for the request. + /// + public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30); + + /// + /// The http headers sent with the request. + /// + internal Headers Headers { get; init; } = new(); + + /// + /// Clones this and returns a new instance + /// + internal ClientOptions Clone() + { + return new ClientOptions + { + BaseUrl = BaseUrl, + HttpClient = HttpClient, + MaxRetries = MaxRetries, + Timeout = Timeout, + Headers = new Headers(new Dictionary(Headers)), + }; + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/RequestOptions.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/RequestOptions.cs new file mode 100644 index 00000000000..3d05f80e95f --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/RequestOptions.cs @@ -0,0 +1,35 @@ +using System; +using System.Net.Http; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public partial class RequestOptions : IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; init; } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; init; } + + /// + /// The http client used to make requests. + /// + public int? MaxRetries { get; init; } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; init; } + + /// + /// The http headers sent with the request. + /// + Headers IRequestOptions.Headers { get; init; } = new(); +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/SeedPathParametersApiException.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/SeedPathParametersApiException.cs new file mode 100644 index 00000000000..cf73d9b109b --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/SeedPathParametersApiException.cs @@ -0,0 +1,18 @@ +namespace SeedPathParameters; + +/// +/// This exception type will be thrown for any non-2XX API responses. +/// +public class SeedPathParametersApiException(string message, int statusCode, object body) + : SeedPathParametersException(message) +{ + /// + /// The error code of the response that triggered the exception. + /// + public int StatusCode => statusCode; + + /// + /// The body of the response that triggered the exception. + /// + public object Body => body; +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/SeedPathParametersException.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/SeedPathParametersException.cs new file mode 100644 index 00000000000..cf842ad3eb2 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/SeedPathParametersException.cs @@ -0,0 +1,11 @@ +using System; + +#nullable enable + +namespace SeedPathParameters; + +/// +/// Base exception class for all exceptions thrown by the SDK. +/// +public class SeedPathParametersException(string message, Exception? innerException = null) + : Exception(message, innerException) { } diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/Version.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/Version.cs new file mode 100644 index 00000000000..82ce383c0e7 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/Public/Version.cs @@ -0,0 +1,6 @@ +namespace SeedPathParameters; + +internal class Version +{ + public const string Current = "0.0.1"; +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/RawClient.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/RawClient.cs new file mode 100644 index 00000000000..8d590867f06 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/Core/RawClient.cs @@ -0,0 +1,189 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; + +namespace SeedPathParameters.Core; + +#nullable enable + +/// +/// Utility class for making raw HTTP requests to the API. +/// +internal class RawClient(ClientOptions clientOptions) +{ + private const int InitialRetryDelayMs = 1000; + private const int MaxRetryDelayMs = 60000; + + /// + /// The client options applied on every request. + /// + public readonly ClientOptions Options = clientOptions; + + public async Task MakeRequestAsync( + BaseApiRequest request, + CancellationToken cancellationToken = default + ) + { + // Apply the request timeout. + var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = request.Options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); + + // Send the request. + return await SendWithRetriesAsync(request, cts.Token); + } + + public record BaseApiRequest + { + public required string BaseUrl { get; init; } + + public required HttpMethod Method { get; init; } + + public required string Path { get; init; } + + public string? ContentType { get; init; } + + public Dictionary Query { get; init; } = new(); + + public Headers Headers { get; init; } = new(); + + public IRequestOptions? Options { get; init; } + } + + /// + /// The request object to be sent for streaming uploads. + /// + public record StreamApiRequest : BaseApiRequest + { + public Stream? Body { get; init; } + } + + /// + /// The request object to be sent for JSON APIs. + /// + public record JsonApiRequest : BaseApiRequest + { + public object? Body { get; init; } + } + + /// + /// The response object returned from the API. + /// + public record ApiResponse + { + public required int StatusCode { get; init; } + + public required HttpResponseMessage Raw { get; init; } + } + + private async Task SendWithRetriesAsync( + BaseApiRequest request, + CancellationToken cancellationToken + ) + { + var httpClient = request.Options?.HttpClient ?? Options.HttpClient; + var maxRetries = request.Options?.MaxRetries ?? Options.MaxRetries; + var response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken); + for (var i = 0; i < maxRetries; i++) + { + if (!ShouldRetry(response)) + { + break; + } + var delayMs = Math.Min(InitialRetryDelayMs * (int)Math.Pow(2, i), MaxRetryDelayMs); + await System.Threading.Tasks.Task.Delay(delayMs, cancellationToken); + response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken); + } + return new ApiResponse { StatusCode = (int)response.StatusCode, Raw = response }; + } + + private static bool ShouldRetry(HttpResponseMessage response) + { + var statusCode = (int)response.StatusCode; + return statusCode is 408 or 429 or >= 500; + } + + private HttpRequestMessage BuildHttpRequest(BaseApiRequest request) + { + var url = BuildUrl(request); + var httpRequest = new HttpRequestMessage(request.Method, url); + switch (request) + { + // Add the request body to the request. + case JsonApiRequest jsonRequest: + { + if (jsonRequest.Body != null) + { + httpRequest.Content = new StringContent( + JsonUtils.Serialize(jsonRequest.Body), + Encoding.UTF8, + "application/json" + ); + } + break; + } + case StreamApiRequest { Body: not null } streamRequest: + httpRequest.Content = new StreamContent(streamRequest.Body); + break; + } + if (request.ContentType != null) + { + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse( + request.ContentType + ); + } + SetHeaders(httpRequest, Options.Headers); + SetHeaders(httpRequest, request.Headers); + SetHeaders(httpRequest, request.Options?.Headers ?? new Headers()); + + return httpRequest; + } + + private static string BuildUrl(BaseApiRequest request) + { + var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl; + var trimmedBaseUrl = baseUrl.TrimEnd('/'); + var trimmedBasePath = request.Path.TrimStart('/'); + var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; + if (request.Query.Count <= 0) + return url; + url += "?"; + url = request.Query.Aggregate( + url, + (current, queryItem) => + { + if (queryItem.Value is System.Collections.IEnumerable collection and not string) + { + var items = collection + .Cast() + .Select(value => $"{queryItem.Key}={value}") + .ToList(); + if (items.Any()) + { + current += string.Join("&", items) + "&"; + } + } + else + { + current += $"{queryItem.Key}={queryItem.Value}&"; + } + return current; + } + ); + url = url[..^1]; + return url; + } + + private static void SetHeaders(HttpRequestMessage httpRequest, Headers headers) + { + foreach (var header in headers) + { + var value = header.Value?.Match(str => str, func => func.Invoke()); + if (value != null) + { + httpRequest.Headers.TryAddWithoutValidation(header.Key, value); + } + } + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParameters.Custom.props b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParameters.Custom.props new file mode 100644 index 00000000000..70df2849401 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParameters.Custom.props @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParameters.csproj b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParameters.csproj new file mode 100644 index 00000000000..ec82eb5bc3f --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParameters.csproj @@ -0,0 +1,53 @@ + + + + + net462;net8.0;net7.0;net6.0;netstandard2.0 + enable + false + 12 + enable + 0.0.1 + $(Version) + $(Version) + README.md + https://github.com/path-parameters/fern + + + + true + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + <_Parameter1>SeedPathParameters.Test + + + + + diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParametersClient.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParametersClient.cs new file mode 100644 index 00000000000..4994df37ff3 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/SeedPathParametersClient.cs @@ -0,0 +1,35 @@ +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public partial class SeedPathParametersClient +{ + private RawClient _client; + + public SeedPathParametersClient(ClientOptions? clientOptions = null) + { + var defaultHeaders = new Headers( + new Dictionary() + { + { "X-Fern-Language", "C#" }, + { "X-Fern-SDK-Name", "SeedPathParameters" }, + { "X-Fern-SDK-Version", Version.Current }, + { "User-Agent", "Fernpath-parameters/0.0.1" }, + } + ); + clientOptions ??= new ClientOptions(); + foreach (var header in defaultHeaders) + { + if (!clientOptions.Headers.ContainsKey(header.Key)) + { + clientOptions.Headers[header.Key] = header.Value; + } + } + _client = new RawClient(clientOptions); + User = new UserClient(_client); + } + + public UserClient User { get; init; } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Requests/GetOrganizationUserRequest.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Requests/GetOrganizationUserRequest.cs new file mode 100644 index 00000000000..341cd196e78 --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Requests/GetOrganizationUserRequest.cs @@ -0,0 +1,13 @@ +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public record GetOrganizationUserRequest +{ + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Requests/GetUsersRequest.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Requests/GetUsersRequest.cs new file mode 100644 index 00000000000..41cfd1b44eb --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Requests/GetUsersRequest.cs @@ -0,0 +1,13 @@ +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public record GetUsersRequest +{ + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Types/User.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Types/User.cs new file mode 100644 index 00000000000..b8777f833fa --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/Types/User.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public record User +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("tags")] + public IEnumerable Tags { get; set; } = new List(); + + public override string ToString() + { + return JsonUtils.Serialize(this); + } +} diff --git a/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/UserClient.cs b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/UserClient.cs new file mode 100644 index 00000000000..409fea08a9e --- /dev/null +++ b/seed/csharp-sdk/path-parameters/src/SeedPathParameters/User/UserClient.cs @@ -0,0 +1,148 @@ +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using SeedPathParameters.Core; + +#nullable enable + +namespace SeedPathParameters; + +public partial class UserClient +{ + private RawClient _client; + + internal UserClient(RawClient client) + { + _client = client; + } + + /// + /// + /// await client.User.GetOrganizationAsync("organizationId"); + /// + /// + public async Task GetOrganizationAsync( + string organizationId, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var response = await _client.MakeRequestAsync( + new RawClient.JsonApiRequest + { + BaseUrl = _client.Options.BaseUrl, + Method = HttpMethod.Get, + Path = $"/user/organizations/{organizationId}", + Options = options, + }, + cancellationToken + ); + var responseBody = await response.Raw.Content.ReadAsStringAsync(); + if (response.StatusCode is >= 200 and < 400) + { + try + { + return JsonUtils.Deserialize(responseBody)!; + } + catch (JsonException e) + { + throw new SeedPathParametersException("Failed to deserialize response", e); + } + } + + throw new SeedPathParametersApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + + /// + /// + /// await client.User.GetUserAsync("userId", new GetUsersRequest()); + /// + /// + public async Task GetUserAsync( + string userId, + GetUsersRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var response = await _client.MakeRequestAsync( + new RawClient.JsonApiRequest + { + BaseUrl = _client.Options.BaseUrl, + Method = HttpMethod.Get, + Path = $"/user/users/{userId}", + Options = options, + }, + cancellationToken + ); + var responseBody = await response.Raw.Content.ReadAsStringAsync(); + if (response.StatusCode is >= 200 and < 400) + { + try + { + return JsonUtils.Deserialize(responseBody)!; + } + catch (JsonException e) + { + throw new SeedPathParametersException("Failed to deserialize response", e); + } + } + + throw new SeedPathParametersApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } + + /// + /// + /// await client.User.GetOrganizationUserAsync( + /// "organizationId", + /// "userId", + /// new GetOrganizationUserRequest() + /// ); + /// + /// + public async Task GetOrganizationUserAsync( + string organizationId, + string userId, + GetOrganizationUserRequest request, + RequestOptions? options = null, + CancellationToken cancellationToken = default + ) + { + var response = await _client.MakeRequestAsync( + new RawClient.JsonApiRequest + { + BaseUrl = _client.Options.BaseUrl, + Method = HttpMethod.Get, + Path = $"/user/organizations/{organizationId}/users/{userId}", + Options = options, + }, + cancellationToken + ); + var responseBody = await response.Raw.Content.ReadAsStringAsync(); + if (response.StatusCode is >= 200 and < 400) + { + try + { + return JsonUtils.Deserialize(responseBody)!; + } + catch (JsonException e) + { + throw new SeedPathParametersException("Failed to deserialize response", e); + } + } + + throw new SeedPathParametersApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + responseBody + ); + } +} diff --git a/seed/fastapi/path-parameters/.mock/definition/api.yml b/seed/fastapi/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/fastapi/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/fastapi/path-parameters/.mock/definition/user.yml b/seed/fastapi/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/fastapi/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/fastapi/path-parameters/.mock/fern.config.json b/seed/fastapi/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/fastapi/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/fastapi/path-parameters/.mock/generators.yml b/seed/fastapi/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/fastapi/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/fastapi/path-parameters/__init__.py b/seed/fastapi/path-parameters/__init__.py new file mode 100644 index 00000000000..03c422787a2 --- /dev/null +++ b/seed/fastapi/path-parameters/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .resources import User, user + +__all__ = ["User", "user"] diff --git a/seed/fastapi/path-parameters/core/__init__.py b/seed/fastapi/path-parameters/core/__init__.py new file mode 100644 index 00000000000..f9c8e44aea0 --- /dev/null +++ b/seed/fastapi/path-parameters/core/__init__.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +from .datetime_utils import serialize_datetime +from .exceptions import ( + FernHTTPException, + UnauthorizedException, + default_exception_handler, + fern_http_exception_handler, + http_exception_handler, +) +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .route_args import route_args +from .security import BearerToken +from .serialization import FieldMetadata, convert_and_respect_annotation_metadata + +__all__ = [ + "BearerToken", + "FernHTTPException", + "FieldMetadata", + "IS_PYDANTIC_V2", + "UnauthorizedException", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "default_exception_handler", + "fern_http_exception_handler", + "http_exception_handler", + "parse_obj_as", + "route_args", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/fastapi/path-parameters/core/abstract_fern_service.py b/seed/fastapi/path-parameters/core/abstract_fern_service.py new file mode 100644 index 00000000000..9966b4876da --- /dev/null +++ b/seed/fastapi/path-parameters/core/abstract_fern_service.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc + +import fastapi + + +class AbstractFernService(abc.ABC): + @classmethod + def _init_fern(cls, router: fastapi.APIRouter) -> None: ... diff --git a/seed/fastapi/path-parameters/core/datetime_utils.py b/seed/fastapi/path-parameters/core/datetime_utils.py new file mode 100644 index 00000000000..47344e9d9cc --- /dev/null +++ b/seed/fastapi/path-parameters/core/datetime_utils.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname( + None + ): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/fastapi/path-parameters/core/exceptions/__init__.py b/seed/fastapi/path-parameters/core/exceptions/__init__.py new file mode 100644 index 00000000000..dae4b8980c1 --- /dev/null +++ b/seed/fastapi/path-parameters/core/exceptions/__init__.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +from .fern_http_exception import FernHTTPException +from .handlers import ( + default_exception_handler, + fern_http_exception_handler, + http_exception_handler, +) +from .unauthorized import UnauthorizedException + +__all__ = [ + "FernHTTPException", + "UnauthorizedException", + "default_exception_handler", + "fern_http_exception_handler", + "http_exception_handler", +] diff --git a/seed/fastapi/path-parameters/core/exceptions/fern_http_exception.py b/seed/fastapi/path-parameters/core/exceptions/fern_http_exception.py new file mode 100644 index 00000000000..81610359a7f --- /dev/null +++ b/seed/fastapi/path-parameters/core/exceptions/fern_http_exception.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc +import fastapi +import typing + + +class FernHTTPException(abc.ABC, fastapi.HTTPException): + def __init__( + self, + status_code: int, + name: typing.Optional[str] = None, + content: typing.Optional[typing.Any] = None, + ): + super().__init__(status_code=status_code) + self.name = name + self.status_code = status_code + self.content = content + + def to_json_response(self) -> fastapi.responses.JSONResponse: + content = fastapi.encoders.jsonable_encoder(self.content, exclude_none=True) + return fastapi.responses.JSONResponse( + content=content, status_code=self.status_code + ) diff --git a/seed/fastapi/path-parameters/core/exceptions/handlers.py b/seed/fastapi/path-parameters/core/exceptions/handlers.py new file mode 100644 index 00000000000..ae1c2741f06 --- /dev/null +++ b/seed/fastapi/path-parameters/core/exceptions/handlers.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +import logging + +import starlette +import starlette.exceptions + +import fastapi + +from .fern_http_exception import FernHTTPException + + +def fern_http_exception_handler( + request: fastapi.requests.Request, + exc: FernHTTPException, + skip_log: bool = False, +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error( + f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc + ) + return exc.to_json_response() + + +def http_exception_handler( + request: fastapi.requests.Request, + exc: starlette.exceptions.HTTPException, + skip_log: bool = False, +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error( + f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc + ) + return FernHTTPException( + status_code=exc.status_code, content=exc.detail + ).to_json_response() + + +def default_exception_handler( + request: fastapi.requests.Request, + exc: Exception, + skip_log: bool = False, +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error( + f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc + ) + return FernHTTPException( + status_code=500, content="Internal Server Error" + ).to_json_response() diff --git a/seed/fastapi/path-parameters/core/exceptions/unauthorized.py b/seed/fastapi/path-parameters/core/exceptions/unauthorized.py new file mode 100644 index 00000000000..32d532e5ef2 --- /dev/null +++ b/seed/fastapi/path-parameters/core/exceptions/unauthorized.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .fern_http_exception import FernHTTPException + + +class UnauthorizedException(FernHTTPException): + """ + This is the exception that is thrown by Fern when auth is not present on a + request. + """ + + def __init__(self, content: typing.Optional[str] = None) -> None: + super().__init__(status_code=401, content=content) diff --git a/seed/fastapi/path-parameters/core/pydantic_utilities.py b/seed/fastapi/path-parameters/core/pydantic_utilities.py new file mode 100644 index 00000000000..fe6359cf503 --- /dev/null +++ b/seed/fastapi/path-parameters/core/pydantic_utilities.py @@ -0,0 +1,273 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict + +import typing_extensions + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + get_origin as get_origin, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_union as is_union, + ) + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + # Allow fields begining with `model_` to be used in the model + protected_namespaces = () + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multi-plexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ( + "exclude_unset" in kwargs and not kwargs["exclude_unset"] + ): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields: typing.Any = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + return super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + +def _union_list_of_pydantic_dicts( + source: typing.List[typing.Any], destination: typing.List[typing.Any] +) -> typing.List[typing.Any]: + converted_list: typing.List[typing.Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] # type: ignore + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append( + _union_list_of_pydantic_dicts(item, destination_value) + ) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel: typing_extensions.TypeAlias = V2RootModel # type: ignore +else: + UniversalRootModel: typing_extensions.TypeAlias = UniversalBaseModel # type: ignore + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator( + pre: bool = False, +) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return pydantic.model_validator(mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + return pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return decorator + + +def universal_field_validator( + field_name: str, pre: bool = False +) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return pydantic.field_validator( + field_name, mode="before" if pre else "after" + )(func) # type: ignore # Pydantic v2 + else: + return pydantic.validator(field_name, pre=pre)(func) # type: ignore # Pydantic v1 + + return decorator + + +PydanticField = typing.Union[ModelField, pydantic.fields.FieldInfo] + + +def _get_model_fields( + model: typing.Type["Model"], +) -> typing.Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return model.model_fields # type: ignore # Pydantic v2 + else: + return model.__fields__ # type: ignore # Pydantic v1 + + +def _get_field_default(field: PydanticField) -> typing.Any: + try: + value = field.get_default() # type: ignore # Pydantic < v1.10.15 + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/seed/fastapi/path-parameters/core/route_args.py b/seed/fastapi/path-parameters/core/route_args.py new file mode 100644 index 00000000000..bd940bf4ddd --- /dev/null +++ b/seed/fastapi/path-parameters/core/route_args.py @@ -0,0 +1,73 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import inspect +import typing + +import typing_extensions + +T = typing.TypeVar("T", bound=typing.Callable[..., typing.Any]) + +FERN_CONFIG_KEY = "__fern" + + +class RouteArgs(typing_extensions.TypedDict): + openapi_extra: typing.Optional[typing.Dict[str, typing.Any]] + tags: typing.Optional[typing.List[typing.Union[str, enum.Enum]]] + include_in_schema: bool + + +DEFAULT_ROUTE_ARGS = RouteArgs(openapi_extra=None, tags=None, include_in_schema=True) + + +def get_route_args( + endpoint_function: typing.Callable[..., typing.Any], *, default_tag: str +) -> RouteArgs: + unwrapped = inspect.unwrap( + endpoint_function, stop=(lambda f: hasattr(f, FERN_CONFIG_KEY)) + ) + route_args = typing.cast( + RouteArgs, getattr(unwrapped, FERN_CONFIG_KEY, DEFAULT_ROUTE_ARGS) + ) + if route_args["tags"] is None: + return RouteArgs( + openapi_extra=route_args["openapi_extra"], + tags=[default_tag], + include_in_schema=route_args["include_in_schema"], + ) + return route_args + + +def route_args( + openapi_extra: typing.Optional[typing.Dict[str, typing.Any]] = None, + tags: typing.Optional[typing.List[typing.Union[str, enum.Enum]]] = None, + include_in_schema: bool = True, +) -> typing.Callable[[T], T]: + """ + this decorator allows you to forward certain args to the FastAPI route decorator. + + usage: + @route_args(openapi_extra=...) + def your_endpoint_method(... + + currently supported args: + - openapi_extra + - tags + + if there's another FastAPI route arg you need to pass through, please + contact the Fern team! + """ + + def decorator(endpoint_function: T) -> T: + setattr( + endpoint_function, + FERN_CONFIG_KEY, + RouteArgs( + openapi_extra=openapi_extra, + tags=tags, + include_in_schema=include_in_schema, + ), + ) + return endpoint_function + + return decorator diff --git a/seed/fastapi/path-parameters/core/security/__init__.py b/seed/fastapi/path-parameters/core/security/__init__.py new file mode 100644 index 00000000000..e69ee6d9c5a --- /dev/null +++ b/seed/fastapi/path-parameters/core/security/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .bearer import BearerToken + +__all__ = ["BearerToken"] diff --git a/seed/fastapi/path-parameters/core/security/bearer.py b/seed/fastapi/path-parameters/core/security/bearer.py new file mode 100644 index 00000000000..023342b668d --- /dev/null +++ b/seed/fastapi/path-parameters/core/security/bearer.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import fastapi + +from ..exceptions import UnauthorizedException + + +class BearerToken: + def __init__(self, token: str): + self.token = token + + +def HTTPBearer(request: fastapi.requests.Request) -> BearerToken: + authorization_header_value = request.headers.get("Authorization") + if not authorization_header_value: + raise UnauthorizedException("Missing Authorization header") + scheme, _, token = authorization_header_value.partition(" ") + if scheme.lower() != "bearer": + raise UnauthorizedException("Authorization header scheme is not bearer") + if not token: + raise UnauthorizedException("Authorization header is missing a token") + return BearerToken(token) diff --git a/seed/fastapi/path-parameters/core/serialization.py b/seed/fastapi/path-parameters/core/serialization.py new file mode 100644 index 00000000000..5679deb8a56 --- /dev/null +++ b/seed/fastapi/path-parameters/core/serialization.py @@ -0,0 +1,276 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import typing_extensions + +import pydantic + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance( + object_, typing.Mapping + ): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[ + _alias_key(key, type_, direction, aliases_to_field_names) + ] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/seed/fastapi/path-parameters/register.py b/seed/fastapi/path-parameters/register.py new file mode 100644 index 00000000000..f0b801ec079 --- /dev/null +++ b/seed/fastapi/path-parameters/register.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import fastapi +from .resources.user.service.service import AbstractUserService +import typing +from fastapi import params +from .core.exceptions.fern_http_exception import FernHTTPException +from .core.exceptions import fern_http_exception_handler +import starlette.exceptions +from .core.exceptions import http_exception_handler +from .core.exceptions import default_exception_handler +from .core.abstract_fern_service import AbstractFernService +import types +import os +import glob +import importlib + + +def register( + _app: fastapi.FastAPI, + *, + user: AbstractUserService, + dependencies: typing.Optional[typing.Sequence[params.Depends]] = None, +) -> None: + _app.include_router(__register_service(user), dependencies=dependencies) + + _app.add_exception_handler(FernHTTPException, fern_http_exception_handler) # type: ignore + _app.add_exception_handler( + starlette.exceptions.HTTPException, http_exception_handler + ) # type: ignore + _app.add_exception_handler(Exception, default_exception_handler) # type: ignore + + +def __register_service(service: AbstractFernService) -> fastapi.APIRouter: + router = fastapi.APIRouter() + type(service)._init_fern(router) + return router + + +def register_validators(module: types.ModuleType) -> None: + validators_directory: str = os.path.dirname(module.__file__) # type: ignore + for path in glob.glob( + os.path.join(validators_directory, "**/*.py"), recursive=True + ): + if os.path.isfile(path): + relative_path = os.path.relpath(path, start=validators_directory) + module_path = ".".join([module.__name__] + relative_path[:-3].split("/")) + importlib.import_module(module_path) diff --git a/seed/fastapi/path-parameters/resources/__init__.py b/seed/fastapi/path-parameters/resources/__init__.py new file mode 100644 index 00000000000..26dc972c93a --- /dev/null +++ b/seed/fastapi/path-parameters/resources/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from . import user +from .user import User + +__all__ = ["User", "user"] diff --git a/seed/fastapi/path-parameters/resources/user/__init__.py b/seed/fastapi/path-parameters/resources/user/__init__.py new file mode 100644 index 00000000000..4d928acf301 --- /dev/null +++ b/seed/fastapi/path-parameters/resources/user/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import User + +__all__ = ["User"] diff --git a/seed/fastapi/path-parameters/resources/user/service/__init__.py b/seed/fastapi/path-parameters/resources/user/service/__init__.py new file mode 100644 index 00000000000..30d7a1adb4c --- /dev/null +++ b/seed/fastapi/path-parameters/resources/user/service/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .service import AbstractUserService + +__all__ = ["AbstractUserService"] diff --git a/seed/fastapi/path-parameters/resources/user/service/service.py b/seed/fastapi/path-parameters/resources/user/service/service.py new file mode 100644 index 00000000000..7c9e9a1bc68 --- /dev/null +++ b/seed/fastapi/path-parameters/resources/user/service/service.py @@ -0,0 +1,170 @@ +# This file was auto-generated by Fern from our API Definition. + +from ....core.abstract_fern_service import AbstractFernService +from ..types.user import User +import abc +import fastapi +import inspect +import typing +from ....core.exceptions.fern_http_exception import FernHTTPException +import logging +import functools +from ....core.route_args import get_route_args + + +class AbstractUserService(AbstractFernService): + """ + AbstractUserService is an abstract class containing the methods that you should implement. + + Each method is associated with an API route, which will be registered + with FastAPI when you register your implementation using Fern's register() + function. + """ + + @abc.abstractmethod + def get_organization(self, *, organization_id: str) -> User: ... + + @abc.abstractmethod + def get_user(self, *, user_id: str) -> User: ... + + @abc.abstractmethod + def get_organization_user(self, *, organization_id: str, user_id: str) -> User: ... + + """ + Below are internal methods used by Fern to register your implementation. + You can ignore them. + """ + + @classmethod + def _init_fern(cls, router: fastapi.APIRouter) -> None: + cls.__init_get_organization(router=router) + cls.__init_get_user(router=router) + cls.__init_get_organization_user(router=router) + + @classmethod + def __init_get_organization(cls, router: fastapi.APIRouter) -> None: + endpoint_function = inspect.signature(cls.get_organization) + new_parameters: typing.List[inspect.Parameter] = [] + for index, (parameter_name, parameter) in enumerate( + endpoint_function.parameters.items() + ): + if index == 0: + new_parameters.append(parameter.replace(default=fastapi.Depends(cls))) + elif parameter_name == "organization_id": + new_parameters.append(parameter.replace(default=fastapi.Path(...))) + else: + new_parameters.append(parameter) + setattr( + cls.get_organization, + "__signature__", + endpoint_function.replace(parameters=new_parameters), + ) + + @functools.wraps(cls.get_organization) + def wrapper(*args: typing.Any, **kwargs: typing.Any) -> User: + try: + return cls.get_organization(*args, **kwargs) + except FernHTTPException as e: + logging.getLogger(f"{cls.__module__}.{cls.__name__}").warn( + f"Endpoint 'get_organization' unexpectedly threw {e.__class__.__name__}. " + + f"If this was intentional, please add {e.__class__.__name__} to " + + "the endpoint's errors list in your Fern Definition." + ) + raise e + + # this is necessary for FastAPI to find forward-ref'ed type hints. + # https://github.com/tiangolo/fastapi/pull/5077 + wrapper.__globals__.update(cls.get_organization.__globals__) + + router.get( + path="/user/organizations/{organization_id}", + response_model=User, + description=AbstractUserService.get_organization.__doc__, + **get_route_args(cls.get_organization, default_tag="user"), + )(wrapper) + + @classmethod + def __init_get_user(cls, router: fastapi.APIRouter) -> None: + endpoint_function = inspect.signature(cls.get_user) + new_parameters: typing.List[inspect.Parameter] = [] + for index, (parameter_name, parameter) in enumerate( + endpoint_function.parameters.items() + ): + if index == 0: + new_parameters.append(parameter.replace(default=fastapi.Depends(cls))) + elif parameter_name == "user_id": + new_parameters.append(parameter.replace(default=fastapi.Path(...))) + else: + new_parameters.append(parameter) + setattr( + cls.get_user, + "__signature__", + endpoint_function.replace(parameters=new_parameters), + ) + + @functools.wraps(cls.get_user) + def wrapper(*args: typing.Any, **kwargs: typing.Any) -> User: + try: + return cls.get_user(*args, **kwargs) + except FernHTTPException as e: + logging.getLogger(f"{cls.__module__}.{cls.__name__}").warn( + f"Endpoint 'get_user' unexpectedly threw {e.__class__.__name__}. " + + f"If this was intentional, please add {e.__class__.__name__} to " + + "the endpoint's errors list in your Fern Definition." + ) + raise e + + # this is necessary for FastAPI to find forward-ref'ed type hints. + # https://github.com/tiangolo/fastapi/pull/5077 + wrapper.__globals__.update(cls.get_user.__globals__) + + router.get( + path="/user/users/{user_id}", + response_model=User, + description=AbstractUserService.get_user.__doc__, + **get_route_args(cls.get_user, default_tag="user"), + )(wrapper) + + @classmethod + def __init_get_organization_user(cls, router: fastapi.APIRouter) -> None: + endpoint_function = inspect.signature(cls.get_organization_user) + new_parameters: typing.List[inspect.Parameter] = [] + for index, (parameter_name, parameter) in enumerate( + endpoint_function.parameters.items() + ): + if index == 0: + new_parameters.append(parameter.replace(default=fastapi.Depends(cls))) + elif parameter_name == "organization_id": + new_parameters.append(parameter.replace(default=fastapi.Path(...))) + elif parameter_name == "user_id": + new_parameters.append(parameter.replace(default=fastapi.Path(...))) + else: + new_parameters.append(parameter) + setattr( + cls.get_organization_user, + "__signature__", + endpoint_function.replace(parameters=new_parameters), + ) + + @functools.wraps(cls.get_organization_user) + def wrapper(*args: typing.Any, **kwargs: typing.Any) -> User: + try: + return cls.get_organization_user(*args, **kwargs) + except FernHTTPException as e: + logging.getLogger(f"{cls.__module__}.{cls.__name__}").warn( + f"Endpoint 'get_organization_user' unexpectedly threw {e.__class__.__name__}. " + + f"If this was intentional, please add {e.__class__.__name__} to " + + "the endpoint's errors list in your Fern Definition." + ) + raise e + + # this is necessary for FastAPI to find forward-ref'ed type hints. + # https://github.com/tiangolo/fastapi/pull/5077 + wrapper.__globals__.update(cls.get_organization_user.__globals__) + + router.get( + path="/user/organizations/{organization_id}/users/{user_id}", + response_model=User, + description=AbstractUserService.get_organization_user.__doc__, + **get_route_args(cls.get_organization_user, default_tag="user"), + )(wrapper) diff --git a/seed/fastapi/path-parameters/resources/user/types/__init__.py b/seed/fastapi/path-parameters/resources/user/types/__init__.py new file mode 100644 index 00000000000..b22b663beed --- /dev/null +++ b/seed/fastapi/path-parameters/resources/user/types/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .user import User + +__all__ = ["User"] diff --git a/seed/fastapi/path-parameters/resources/user/types/user.py b/seed/fastapi/path-parameters/resources/user/types/user.py new file mode 100644 index 00000000000..f25183bc31a --- /dev/null +++ b/seed/fastapi/path-parameters/resources/user/types/user.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +from ....core.pydantic_utilities import UniversalBaseModel +import typing +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +import pydantic + + +class User(UniversalBaseModel): + name: str + tags: typing.List[str] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="forbid" + ) # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.forbid diff --git a/seed/fastapi/path-parameters/snippet-templates.json b/seed/fastapi/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/fastapi/path-parameters/snippet.json b/seed/fastapi/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/path-parameters/.github/workflows/ci.yml b/seed/go-fiber/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/path-parameters/.mock/definition/api.yml b/seed/go-fiber/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/go-fiber/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/go-fiber/path-parameters/.mock/definition/user.yml b/seed/go-fiber/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/go-fiber/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/go-fiber/path-parameters/.mock/fern.config.json b/seed/go-fiber/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-fiber/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-fiber/path-parameters/.mock/generators.yml b/seed/go-fiber/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/go-fiber/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/go-fiber/path-parameters/go.mod b/seed/go-fiber/path-parameters/go.mod new file mode 100644 index 00000000000..ab4c657f716 --- /dev/null +++ b/seed/go-fiber/path-parameters/go.mod @@ -0,0 +1,8 @@ +module github.com/path-parameters/fern + +go 1.13 + +require ( + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-fiber/path-parameters/go.sum b/seed/go-fiber/path-parameters/go.sum new file mode 100644 index 00000000000..fc3dd9e67e8 --- /dev/null +++ b/seed/go-fiber/path-parameters/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-fiber/path-parameters/internal/extra_properties.go b/seed/go-fiber/path-parameters/internal/extra_properties.go new file mode 100644 index 00000000000..540c3fd89ee --- /dev/null +++ b/seed/go-fiber/path-parameters/internal/extra_properties.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-fiber/path-parameters/internal/extra_properties_test.go b/seed/go-fiber/path-parameters/internal/extra_properties_test.go new file mode 100644 index 00000000000..aa2510ee512 --- /dev/null +++ b/seed/go-fiber/path-parameters/internal/extra_properties_test.go @@ -0,0 +1,228 @@ +package internal + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-fiber/path-parameters/internal/stringer.go b/seed/go-fiber/path-parameters/internal/stringer.go new file mode 100644 index 00000000000..312801851e0 --- /dev/null +++ b/seed/go-fiber/path-parameters/internal/stringer.go @@ -0,0 +1,13 @@ +package internal + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/path-parameters/internal/time.go b/seed/go-fiber/path-parameters/internal/time.go new file mode 100644 index 00000000000..ab0e269fade --- /dev/null +++ b/seed/go-fiber/path-parameters/internal/time.go @@ -0,0 +1,137 @@ +package internal + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/path-parameters/snippet-templates.json b/seed/go-fiber/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/path-parameters/snippet.json b/seed/go-fiber/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/path-parameters/user.go b/seed/go-fiber/path-parameters/user.go new file mode 100644 index 00000000000..c2f60b8f5d9 --- /dev/null +++ b/seed/go-fiber/path-parameters/user.go @@ -0,0 +1,62 @@ +// This file was auto-generated by Fern from our API Definition. + +package pathparameters + +import ( + json "encoding/json" + fmt "fmt" + internal "github.com/path-parameters/fern/internal" +) + +type GetOrganizationUserRequest struct { +} + +type GetUsersRequest struct { +} + +type User struct { + Name string `json:"name" url:"name"` + Tags []string `json:"tags,omitempty" url:"tags,omitempty"` + + extraProperties map[string]interface{} +} + +func (u *User) GetName() string { + if u == nil { + return "" + } + return u.Name +} + +func (u *User) GetTags() []string { + if u == nil { + return nil + } + return u.Tags +} + +func (u *User) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + return nil +} + +func (u *User) String() string { + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-model/path-parameters/.github/workflows/ci.yml b/seed/go-model/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/path-parameters/.mock/definition/api.yml b/seed/go-model/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/go-model/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/go-model/path-parameters/.mock/definition/user.yml b/seed/go-model/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/go-model/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/go-model/path-parameters/.mock/fern.config.json b/seed/go-model/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-model/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-model/path-parameters/.mock/generators.yml b/seed/go-model/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/go-model/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/go-model/path-parameters/go.mod b/seed/go-model/path-parameters/go.mod new file mode 100644 index 00000000000..ab4c657f716 --- /dev/null +++ b/seed/go-model/path-parameters/go.mod @@ -0,0 +1,8 @@ +module github.com/path-parameters/fern + +go 1.13 + +require ( + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-model/path-parameters/go.sum b/seed/go-model/path-parameters/go.sum new file mode 100644 index 00000000000..fc3dd9e67e8 --- /dev/null +++ b/seed/go-model/path-parameters/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-model/path-parameters/internal/extra_properties.go b/seed/go-model/path-parameters/internal/extra_properties.go new file mode 100644 index 00000000000..540c3fd89ee --- /dev/null +++ b/seed/go-model/path-parameters/internal/extra_properties.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-model/path-parameters/internal/extra_properties_test.go b/seed/go-model/path-parameters/internal/extra_properties_test.go new file mode 100644 index 00000000000..aa2510ee512 --- /dev/null +++ b/seed/go-model/path-parameters/internal/extra_properties_test.go @@ -0,0 +1,228 @@ +package internal + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-model/path-parameters/internal/stringer.go b/seed/go-model/path-parameters/internal/stringer.go new file mode 100644 index 00000000000..312801851e0 --- /dev/null +++ b/seed/go-model/path-parameters/internal/stringer.go @@ -0,0 +1,13 @@ +package internal + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/path-parameters/internal/time.go b/seed/go-model/path-parameters/internal/time.go new file mode 100644 index 00000000000..ab0e269fade --- /dev/null +++ b/seed/go-model/path-parameters/internal/time.go @@ -0,0 +1,137 @@ +package internal + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/path-parameters/snippet-templates.json b/seed/go-model/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/path-parameters/snippet.json b/seed/go-model/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/path-parameters/user.go b/seed/go-model/path-parameters/user.go new file mode 100644 index 00000000000..4a486723ed7 --- /dev/null +++ b/seed/go-model/path-parameters/user.go @@ -0,0 +1,56 @@ +// This file was auto-generated by Fern from our API Definition. + +package pathparameters + +import ( + json "encoding/json" + fmt "fmt" + internal "github.com/path-parameters/fern/internal" +) + +type User struct { + Name string `json:"name" url:"name"` + Tags []string `json:"tags,omitempty" url:"tags,omitempty"` + + extraProperties map[string]interface{} +} + +func (u *User) GetName() string { + if u == nil { + return "" + } + return u.Name +} + +func (u *User) GetTags() []string { + if u == nil { + return nil + } + return u.Tags +} + +func (u *User) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + return nil +} + +func (u *User) String() string { + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/path-parameters/.github/workflows/ci.yml b/seed/go-sdk/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/path-parameters/.mock/definition/api.yml b/seed/go-sdk/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/go-sdk/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/go-sdk/path-parameters/.mock/definition/user.yml b/seed/go-sdk/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/go-sdk/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/go-sdk/path-parameters/.mock/fern.config.json b/seed/go-sdk/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-sdk/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-sdk/path-parameters/.mock/generators.yml b/seed/go-sdk/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/go-sdk/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/go-sdk/path-parameters/client/client.go b/seed/go-sdk/path-parameters/client/client.go new file mode 100644 index 00000000000..a215b77dee2 --- /dev/null +++ b/seed/go-sdk/path-parameters/client/client.go @@ -0,0 +1,34 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/path-parameters/fern/core" + internal "github.com/path-parameters/fern/internal" + option "github.com/path-parameters/fern/option" + user "github.com/path-parameters/fern/user" + http "net/http" +) + +type Client struct { + baseURL string + caller *internal.Caller + header http.Header + + User *user.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: internal.NewCaller( + &internal.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + User: user.NewClient(opts...), + } +} diff --git a/seed/go-sdk/path-parameters/client/client_test.go b/seed/go-sdk/path-parameters/client/client_test.go new file mode 100644 index 00000000000..bae6e0e23e9 --- /dev/null +++ b/seed/go-sdk/path-parameters/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/path-parameters/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/path-parameters/core/api_error.go b/seed/go-sdk/path-parameters/core/api_error.go new file mode 100644 index 00000000000..dc4190ca1cd --- /dev/null +++ b/seed/go-sdk/path-parameters/core/api_error.go @@ -0,0 +1,42 @@ +package core + +import "fmt" + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} diff --git a/seed/go-sdk/path-parameters/core/http.go b/seed/go-sdk/path-parameters/core/http.go new file mode 100644 index 00000000000..b553350b84e --- /dev/null +++ b/seed/go-sdk/path-parameters/core/http.go @@ -0,0 +1,8 @@ +package core + +import "net/http" + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} diff --git a/seed/go-sdk/path-parameters/core/request_option.go b/seed/go-sdk/path-parameters/core/request_option.go new file mode 100644 index 00000000000..ab1af856a9b --- /dev/null +++ b/seed/go-sdk/path-parameters/core/request_option.go @@ -0,0 +1,108 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" + url "net/url" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + BodyProperties map[string]interface{} + QueryParameters url.Values + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + BodyProperties: make(map[string]interface{}), + QueryParameters: make(url.Values), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/path-parameters/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// BodyPropertiesOption implements the RequestOption interface. +type BodyPropertiesOption struct { + BodyProperties map[string]interface{} +} + +func (b *BodyPropertiesOption) applyRequestOptions(opts *RequestOptions) { + opts.BodyProperties = b.BodyProperties +} + +// QueryParametersOption implements the RequestOption interface. +type QueryParametersOption struct { + QueryParameters url.Values +} + +func (q *QueryParametersOption) applyRequestOptions(opts *RequestOptions) { + opts.QueryParameters = q.QueryParameters +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/path-parameters/dynamic-snippets/example0/snippet.go b/seed/go-sdk/path-parameters/dynamic-snippets/example0/snippet.go new file mode 100644 index 00000000000..e0494a50f95 --- /dev/null +++ b/seed/go-sdk/path-parameters/dynamic-snippets/example0/snippet.go @@ -0,0 +1,14 @@ +package example + +import ( + client "github.com/path-parameters/fern/client" + context "context" +) + +func do() () { + client := client.NewClient() + client.User.GetOrganization( + context.TODO(), + "organizationId", + ) +} diff --git a/seed/go-sdk/path-parameters/dynamic-snippets/example1/snippet.go b/seed/go-sdk/path-parameters/dynamic-snippets/example1/snippet.go new file mode 100644 index 00000000000..f6c39cf8fb7 --- /dev/null +++ b/seed/go-sdk/path-parameters/dynamic-snippets/example1/snippet.go @@ -0,0 +1,16 @@ +package example + +import ( + client "github.com/path-parameters/fern/client" + context "context" + fern "github.com/path-parameters/fern" +) + +func do() () { + client := client.NewClient() + client.User.GetUser( + context.TODO(), + "userId", + &fern.GetUsersRequest{}, + ) +} diff --git a/seed/go-sdk/path-parameters/dynamic-snippets/example2/snippet.go b/seed/go-sdk/path-parameters/dynamic-snippets/example2/snippet.go new file mode 100644 index 00000000000..b459f85c6d3 --- /dev/null +++ b/seed/go-sdk/path-parameters/dynamic-snippets/example2/snippet.go @@ -0,0 +1,17 @@ +package example + +import ( + client "github.com/path-parameters/fern/client" + context "context" + fern "github.com/path-parameters/fern" +) + +func do() () { + client := client.NewClient() + client.User.GetOrganizationUser( + context.TODO(), + "organizationId", + "userId", + &fern.GetOrganizationUserRequest{}, + ) +} diff --git a/seed/go-sdk/path-parameters/file_param.go b/seed/go-sdk/path-parameters/file_param.go new file mode 100644 index 00000000000..b2bee528b8c --- /dev/null +++ b/seed/go-sdk/path-parameters/file_param.go @@ -0,0 +1,41 @@ +package pathparameters + +import ( + "io" +) + +// FileParam is a file type suitable for multipart/form-data uploads. +type FileParam struct { + io.Reader + filename string + contentType string +} + +// FileParamOption adapts the behavior of the FileParam. No options are +// implemented yet, but this interface allows for future extensibility. +type FileParamOption interface { + apply() +} + +// NewFileParam returns a *FileParam type suitable for multipart/form-data uploads. All file +// upload endpoints accept a simple io.Reader, which is usually created by opening a file +// via os.Open. +// +// However, some endpoints require additional metadata about the file such as a specific +// Content-Type or custom filename. FileParam makes it easier to create the correct type +// signature for these endpoints. +func NewFileParam( + reader io.Reader, + filename string, + contentType string, + opts ...FileParamOption, +) *FileParam { + return &FileParam{ + Reader: reader, + filename: filename, + contentType: contentType, + } +} + +func (f *FileParam) Name() string { return f.filename } +func (f *FileParam) ContentType() string { return f.contentType } diff --git a/seed/go-sdk/path-parameters/go.mod b/seed/go-sdk/path-parameters/go.mod new file mode 100644 index 00000000000..8e43fc078ac --- /dev/null +++ b/seed/go-sdk/path-parameters/go.mod @@ -0,0 +1,9 @@ +module github.com/path-parameters/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/path-parameters/go.sum b/seed/go-sdk/path-parameters/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/path-parameters/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/path-parameters/internal/caller.go b/seed/go-sdk/path-parameters/internal/caller.go new file mode 100644 index 00000000000..391cdf34939 --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/caller.go @@ -0,0 +1,238 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "reflect" + "strings" + + "github.com/path-parameters/fern/core" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client core.HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client core.HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient core.HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + BodyProperties map[string]interface{} + QueryParameters url.Values + Client core.HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + url := buildURL(params.URL, params.QueryParameters) + req, err := newRequest( + ctx, + url, + params.Method, + params.Headers, + params.Request, + params.BodyProperties, + ) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// buildURL constructs the final URL by appending the given query parameters (if any). +func buildURL( + url string, + queryParameters url.Values, +) string { + if len(queryParameters) == 0 { + return url + } + if strings.ContainsRune(url, '?') { + url += "&" + } else { + url += "?" + } + url += queryParameters.Encode() + return url +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, + bodyProperties map[string]interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request, bodyProperties) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}, bodyProperties map[string]interface{}) (io.Reader, error) { + if isNil(request) { + if len(bodyProperties) == 0 { + return nil, nil + } + requestBytes, err := json.Marshal(bodyProperties) + if err != nil { + return nil, err + } + return bytes.NewReader(requestBytes), nil + } + if body, ok := request.(io.Reader); ok { + return body, nil + } + requestBytes, err := MarshalJSONWithExtraProperties(request, bodyProperties) + if err != nil { + return nil, err + } + return bytes.NewReader(requestBytes), nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return core.NewAPIError(response.StatusCode, nil) + } + return core.NewAPIError(response.StatusCode, errors.New(string(bytes))) +} + +// isNil is used to determine if the request value is equal to nil (i.e. an interface +// value that holds a nil concrete value is itself non-nil). +func isNil(value interface{}) bool { + return value == nil || reflect.ValueOf(value).IsNil() +} diff --git a/seed/go-sdk/path-parameters/internal/caller_test.go b/seed/go-sdk/path-parameters/internal/caller_test.go new file mode 100644 index 00000000000..48b5547424c --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/caller_test.go @@ -0,0 +1,391 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/path-parameters/fern/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + givePathSuffix string + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + giveQueryParams url.Values + giveBodyProperties map[string]interface{} + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` + ExtraBodyProperties map[string]interface{} `json:"extraBodyProperties,omitempty"` + QueryParameters url.Values `json:"queryParameters,omitempty"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *core.APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET success with query", + givePathSuffix: "?limit=1", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + QueryParameters: url.Values{ + "limit": []string{"1"}, + }, + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: core.NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST empty body", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: nil, + wantError: core.NewAPIError( + http.StatusBadRequest, + errors.New("invalid request"), + ), + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: core.NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + { + description: "POST extra properties", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: new(Request), + giveBodyProperties: map[string]interface{}{ + "key": "value", + }, + wantResponse: &Response{ + ExtraBodyProperties: map[string]interface{}{ + "key": "value", + }, + }, + }, + { + description: "GET extra query parameters", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveQueryParams: url.Values{ + "extra": []string{"true"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + QueryParameters: url.Values{ + "extra": []string{"true"}, + }, + }, + }, + { + description: "GET merge extra query parameters", + givePathSuffix: "?limit=1", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveQueryParams: url.Values{ + "extra": []string{"true"}, + }, + wantResponse: &Response{ + Id: "123", + QueryParameters: url.Values{ + "limit": []string{"1"}, + "extra": []string{"true"}, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL + test.givePathSuffix, + Method: test.giveMethod, + Headers: test.giveHeader, + BodyProperties: test.giveBodyProperties, + QueryParameters: test.giveQueryParams, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + request := new(Request) + + bytes, err := io.ReadAll(r.Body) + if tc.giveRequest == nil { + require.Empty(t, bytes) + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("invalid request")) + require.NoError(t, err) + return + } + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &core.APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + extraBodyProperties := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &extraBodyProperties)) + delete(extraBodyProperties, "id") + + response := &Response{ + Id: request.Id, + ExtraBodyProperties: extraBodyProperties, + QueryParameters: r.URL.Query(), + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = core.NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/path-parameters/internal/error_decoder.go b/seed/go-sdk/path-parameters/internal/error_decoder.go new file mode 100644 index 00000000000..0cc61d5dbdc --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/error_decoder.go @@ -0,0 +1,45 @@ +package internal + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + + "github.com/path-parameters/fern/core" +) + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *core.APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// ErrorCodes maps HTTP status codes to error constructors. +type ErrorCodes map[int]func(*core.APIError) error + +// NewErrorDecoder returns a new ErrorDecoder backed by the given error codes. +func NewErrorDecoder(errorCodes ErrorCodes) ErrorDecoder { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return fmt.Errorf("failed to read error from response body: %w", err) + } + apiError := core.NewAPIError( + statusCode, + errors.New(string(raw)), + ) + newErrorFunc, ok := errorCodes[statusCode] + if !ok { + // This status code isn't recognized, so we return + // the API error as-is. + return apiError + } + customError := newErrorFunc(apiError) + if err := json.NewDecoder(bytes.NewReader(raw)).Decode(customError); err != nil { + // If we fail to decode the error, we return the + // API error as-is. + return apiError + } + return customError + } +} diff --git a/seed/go-sdk/path-parameters/internal/error_decoder_test.go b/seed/go-sdk/path-parameters/internal/error_decoder_test.go new file mode 100644 index 00000000000..1ad06b3f81d --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/error_decoder_test.go @@ -0,0 +1,55 @@ +package internal + +import ( + "bytes" + "errors" + "net/http" + "testing" + + "github.com/path-parameters/fern/core" + "github.com/stretchr/testify/assert" +) + +func TestErrorDecoder(t *testing.T) { + decoder := NewErrorDecoder( + ErrorCodes{ + http.StatusNotFound: func(apiError *core.APIError) error { + return &NotFoundError{APIError: apiError} + }, + }) + + tests := []struct { + description string + giveStatusCode int + giveBody string + wantError error + }{ + { + description: "unrecognized status code", + giveStatusCode: http.StatusInternalServerError, + giveBody: "Internal Server Error", + wantError: core.NewAPIError(http.StatusInternalServerError, errors.New("Internal Server Error")), + }, + { + description: "not found with valid JSON", + giveStatusCode: http.StatusNotFound, + giveBody: `{"message": "Resource not found"}`, + wantError: &NotFoundError{ + APIError: core.NewAPIError(http.StatusNotFound, errors.New(`{"message": "Resource not found"}`)), + Message: "Resource not found", + }, + }, + { + description: "not found with invalid JSON", + giveStatusCode: http.StatusNotFound, + giveBody: `Resource not found`, + wantError: core.NewAPIError(http.StatusNotFound, errors.New("Resource not found")), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + assert.Equal(t, tt.wantError, decoder(tt.giveStatusCode, bytes.NewReader([]byte(tt.giveBody)))) + }) + } +} diff --git a/seed/go-sdk/path-parameters/internal/extra_properties.go b/seed/go-sdk/path-parameters/internal/extra_properties.go new file mode 100644 index 00000000000..540c3fd89ee --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/extra_properties.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-sdk/path-parameters/internal/extra_properties_test.go b/seed/go-sdk/path-parameters/internal/extra_properties_test.go new file mode 100644 index 00000000000..aa2510ee512 --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/extra_properties_test.go @@ -0,0 +1,228 @@ +package internal + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-sdk/path-parameters/internal/http.go b/seed/go-sdk/path-parameters/internal/http.go new file mode 100644 index 00000000000..768968bd621 --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/http.go @@ -0,0 +1,48 @@ +package internal + +import ( + "fmt" + "net/http" + "net/url" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// ResolveBaseURL resolves the base URL from the given arguments, +// preferring the first non-empty value. +func ResolveBaseURL(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} diff --git a/seed/go-sdk/path-parameters/internal/query.go b/seed/go-sdk/path-parameters/internal/query.go new file mode 100644 index 00000000000..6129e71ffe5 --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/query.go @@ -0,0 +1,231 @@ +package internal + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + value := sv.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), name); err != nil { + return err + } + } else { + values.Add(name, valueString(value, opts, sf)) + } + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsZero() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// isStructPointer returns true if the given reflect.Value is a pointer to a struct. +func isStructPointer(v reflect.Value) bool { + return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/path-parameters/internal/query_test.go b/seed/go-sdk/path-parameters/internal/query_test.go new file mode 100644 index 00000000000..2e58ccadde1 --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/query_test.go @@ -0,0 +1,187 @@ +package internal + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("omitempty with non-pointer zero value", func(t *testing.T) { + type enum string + + type example struct { + Enum enum `json:"enum,omitempty" url:"enum,omitempty"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("object array", func(t *testing.T) { + type object struct { + Key string `json:"key" url:"key"` + Value string `json:"value" url:"value"` + } + type example struct { + Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` + } + + values, err := QueryValues( + &example{ + Objects: []*object{ + { + Key: "hello", + Value: "world", + }, + { + Key: "foo", + Value: "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) + }) +} diff --git a/seed/go-sdk/path-parameters/internal/retrier.go b/seed/go-sdk/path-parameters/internal/retrier.go new file mode 100644 index 00000000000..6040147154b --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/retrier.go @@ -0,0 +1,165 @@ +package internal + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/path-parameters/internal/retrier_test.go b/seed/go-sdk/path-parameters/internal/retrier_test.go new file mode 100644 index 00000000000..7f2cecf02eb --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/retrier_test.go @@ -0,0 +1,211 @@ +package internal + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/path-parameters/fern/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type RetryTestCase struct { + description string + + giveAttempts uint + giveStatusCodes []int + giveResponse *Response + + wantResponse *Response + wantError *core.APIError +} + +func TestRetrier(t *testing.T) { + tests := []*RetryTestCase{ + { + description: "retry request succeeds after multiple failures", + giveAttempts: 3, + giveStatusCodes: []int{ + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusOK, + }, + giveResponse: &Response{ + Id: "1", + }, + wantResponse: &Response{ + Id: "1", + }, + }, + { + description: "retry request fails if MaxAttempts is exceeded", + giveAttempts: 3, + giveStatusCodes: []int{ + http.StatusRequestTimeout, + http.StatusRequestTimeout, + http.StatusRequestTimeout, + http.StatusOK, + }, + wantError: &core.APIError{ + StatusCode: http.StatusRequestTimeout, + }, + }, + { + description: "retry durations increase exponentially and stay within the min and max delay values", + giveAttempts: 4, + giveStatusCodes: []int{ + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusServiceUnavailable, + http.StatusOK, + }, + }, + { + description: "retry does not occur on status code 404", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusNotFound, http.StatusOK}, + wantError: &core.APIError{ + StatusCode: http.StatusNotFound, + }, + }, + { + description: "retries occur on status code 429", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusTooManyRequests, http.StatusOK}, + }, + { + description: "retries occur on status code 408", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusRequestTimeout, http.StatusOK}, + }, + { + description: "retries occur on status code 500", + giveAttempts: 2, + giveStatusCodes: []int{http.StatusInternalServerError, http.StatusOK}, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + var ( + test = tc + server = newTestRetryServer(t, test) + client = server.Client() + ) + + t.Parallel() + + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: http.MethodGet, + Request: &Request{}, + Response: &response, + MaxAttempts: test.giveAttempts, + ResponseIsOptional: true, + }, + ) + + if test.wantError != nil { + require.IsType(t, err, &core.APIError{}) + expectedErrorCode := test.wantError.StatusCode + actualErrorCode := err.(*core.APIError).StatusCode + assert.Equal(t, expectedErrorCode, actualErrorCode) + return + } + + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +// newTestRetryServer returns a new *httptest.Server configured with the +// given test parameters, suitable for testing retries. +func newTestRetryServer(t *testing.T, tc *RetryTestCase) *httptest.Server { + var index int + timestamps := make([]time.Time, 0, len(tc.giveStatusCodes)) + + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + timestamps = append(timestamps, time.Now()) + if index > 0 && index < len(expectedRetryDurations) { + // Ensure that the duration between retries increases exponentially, + // and that it is within the minimum and maximum retry delay values. + actualDuration := timestamps[index].Sub(timestamps[index-1]) + expectedDurationMin := expectedRetryDurations[index-1] * 75 / 100 + expectedDurationMax := expectedRetryDurations[index-1] * 125 / 100 + assert.True( + t, + actualDuration >= expectedDurationMin && actualDuration <= expectedDurationMax, + "expected duration to be in range [%v, %v], got %v", + expectedDurationMin, + expectedDurationMax, + actualDuration, + ) + assert.LessOrEqual( + t, + actualDuration, + maxRetryDelay, + "expected duration to be less than the maxRetryDelay (%v), got %v", + maxRetryDelay, + actualDuration, + ) + assert.GreaterOrEqual( + t, + actualDuration, + minRetryDelay, + "expected duration to be greater than the minRetryDelay (%v), got %v", + minRetryDelay, + actualDuration, + ) + } + + request := new(Request) + bytes, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + require.LessOrEqual(t, index, len(tc.giveStatusCodes)) + + statusCode := tc.giveStatusCodes[index] + w.WriteHeader(statusCode) + + if tc.giveResponse != nil && statusCode == http.StatusOK { + bytes, err = json.Marshal(tc.giveResponse) + require.NoError(t, err) + _, err = w.Write(bytes) + require.NoError(t, err) + } + + index++ + }, + ), + ) +} + +// expectedRetryDurations holds an array of calculated retry durations, +// where the index of the array should correspond to the retry attempt. +// +// Values are calculated based off of `minRetryDelay + minRetryDelay*i*i`, with +// a max and min value of 5000ms and 500ms respectively. +var expectedRetryDurations = []time.Duration{ + 500 * time.Millisecond, + 1000 * time.Millisecond, + 2500 * time.Millisecond, + 5000 * time.Millisecond, + 5000 * time.Millisecond, +} diff --git a/seed/go-sdk/path-parameters/internal/stringer.go b/seed/go-sdk/path-parameters/internal/stringer.go new file mode 100644 index 00000000000..312801851e0 --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/stringer.go @@ -0,0 +1,13 @@ +package internal + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/path-parameters/internal/time.go b/seed/go-sdk/path-parameters/internal/time.go new file mode 100644 index 00000000000..ab0e269fade --- /dev/null +++ b/seed/go-sdk/path-parameters/internal/time.go @@ -0,0 +1,137 @@ +package internal + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/path-parameters/option/request_option.go b/seed/go-sdk/path-parameters/option/request_option.go new file mode 100644 index 00000000000..e5661652492 --- /dev/null +++ b/seed/go-sdk/path-parameters/option/request_option.go @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/path-parameters/fern/core" + http "net/http" + url "net/url" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithBodyProperties adds the given body properties to the request. +func WithBodyProperties(bodyProperties map[string]interface{}) *core.BodyPropertiesOption { + copiedBodyProperties := make(map[string]interface{}, len(bodyProperties)) + for key, value := range bodyProperties { + copiedBodyProperties[key] = value + } + return &core.BodyPropertiesOption{ + BodyProperties: copiedBodyProperties, + } +} + +// WithQueryParameters adds the given query parameters to the request. +func WithQueryParameters(queryParameters url.Values) *core.QueryParametersOption { + copiedQueryParameters := make(url.Values, len(queryParameters)) + for key, values := range queryParameters { + copiedQueryParameters[key] = values + } + return &core.QueryParametersOption{ + QueryParameters: copiedQueryParameters, + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/path-parameters/pointer.go b/seed/go-sdk/path-parameters/pointer.go new file mode 100644 index 00000000000..b91ea4b241a --- /dev/null +++ b/seed/go-sdk/path-parameters/pointer.go @@ -0,0 +1,132 @@ +package pathparameters + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/path-parameters/snippet-templates.json b/seed/go-sdk/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/path-parameters/snippet.json b/seed/go-sdk/path-parameters/snippet.json new file mode 100644 index 00000000000..be84aa9bdec --- /dev/null +++ b/seed/go-sdk/path-parameters/snippet.json @@ -0,0 +1,37 @@ +{ + "endpoints": [ + { + "id": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganization" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfernclient \"github.com/path-parameters/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.User.GetOrganization(\n\tcontext.TODO(),\n\t\"organizationId\",\n)\n" + } + }, + { + "id": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganizationUser" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/path-parameters/fern\"\n\tfernclient \"github.com/path-parameters/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.User.GetOrganizationUser(\n\tcontext.TODO(),\n\t\"organizationId\",\n\t\"userId\",\n\t\u0026fern.GetOrganizationUserRequest{},\n)\n" + } + }, + { + "id": { + "path": "/user/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getUser" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/path-parameters/fern\"\n\tfernclient \"github.com/path-parameters/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.User.GetUser(\n\tcontext.TODO(),\n\t\"userId\",\n\t\u0026fern.GetUsersRequest{},\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/path-parameters/user.go b/seed/go-sdk/path-parameters/user.go new file mode 100644 index 00000000000..26f4f8bd7de --- /dev/null +++ b/seed/go-sdk/path-parameters/user.go @@ -0,0 +1,69 @@ +// This file was auto-generated by Fern from our API Definition. + +package pathparameters + +import ( + json "encoding/json" + fmt "fmt" + internal "github.com/path-parameters/fern/internal" +) + +type GetOrganizationUserRequest struct { +} + +type GetUsersRequest struct { +} + +type User struct { + Name string `json:"name" url:"name"` + Tags []string `json:"tags,omitempty" url:"tags,omitempty"` + + extraProperties map[string]interface{} + rawJSON json.RawMessage +} + +func (u *User) GetName() string { + if u == nil { + return "" + } + return u.Name +} + +func (u *User) GetTags() []string { + if u == nil { + return nil + } + return u.Tags +} + +func (u *User) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + extraProperties, err := internal.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + u.rawJSON = json.RawMessage(data) + return nil +} + +func (u *User) String() string { + if len(u.rawJSON) > 0 { + if value, err := internal.StringifyJSON(u.rawJSON); err == nil { + return value + } + } + if value, err := internal.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/path-parameters/user/client.go b/seed/go-sdk/path-parameters/user/client.go new file mode 100644 index 00000000000..8edeb3987a7 --- /dev/null +++ b/seed/go-sdk/path-parameters/user/client.go @@ -0,0 +1,153 @@ +// This file was auto-generated by Fern from our API Definition. + +package user + +import ( + context "context" + fern "github.com/path-parameters/fern" + core "github.com/path-parameters/fern/core" + internal "github.com/path-parameters/fern/internal" + option "github.com/path-parameters/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *internal.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: internal.NewCaller( + &internal.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetOrganization( + ctx context.Context, + organizationId string, + opts ...option.RequestOption, +) (*fern.User, error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + c.baseURL, + "", + ) + endpointURL := internal.EncodeURL( + baseURL+"/user/organizations/%v", + organizationId, + ) + headers := internal.MergeHeaders( + c.header.Clone(), + options.ToHeader(), + ) + + var response *fern.User + if err := c.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} + +func (c *Client) GetUser( + ctx context.Context, + userId string, + request *fern.GetUsersRequest, + opts ...option.RequestOption, +) (*fern.User, error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + c.baseURL, + "", + ) + endpointURL := internal.EncodeURL( + baseURL+"/user/users/%v", + userId, + ) + headers := internal.MergeHeaders( + c.header.Clone(), + options.ToHeader(), + ) + + var response *fern.User + if err := c.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} + +func (c *Client) GetOrganizationUser( + ctx context.Context, + organizationId string, + userId string, + request *fern.GetOrganizationUserRequest, + opts ...option.RequestOption, +) (*fern.User, error) { + options := core.NewRequestOptions(opts...) + baseURL := internal.ResolveBaseURL( + options.BaseURL, + c.baseURL, + "", + ) + endpointURL := internal.EncodeURL( + baseURL+"/user/organizations/%v/users/%v", + organizationId, + userId, + ) + headers := internal.MergeHeaders( + c.header.Clone(), + options.ToHeader(), + ) + + var response *fern.User + if err := c.caller.Call( + ctx, + &internal.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: headers, + MaxAttempts: options.MaxAttempts, + BodyProperties: options.BodyProperties, + QueryParameters: options.QueryParameters, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/java-model/path-parameters/.github/workflows/ci.yml b/seed/java-model/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..8598a73092a --- /dev/null +++ b/seed/java-model/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-model/path-parameters/.gitignore b/seed/java-model/path-parameters/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-model/path-parameters/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-model/path-parameters/.mock/definition/api.yml b/seed/java-model/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/java-model/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/java-model/path-parameters/.mock/definition/user.yml b/seed/java-model/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/java-model/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/java-model/path-parameters/.mock/fern.config.json b/seed/java-model/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-model/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-model/path-parameters/.mock/generators.yml b/seed/java-model/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/java-model/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/java-model/path-parameters/build.gradle b/seed/java-model/path-parameters/build.gradle new file mode 100644 index 00000000000..7dbfa90f51b --- /dev/null +++ b/seed/java-model/path-parameters/build.gradle @@ -0,0 +1,67 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +spotless { + java { + palantirJavaFormat() + } +} + +java { + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'path-parameters' + version = '0.0.1' + from components.java + pom { + scm { + connection = 'scm:git:git://github.com/path-parameters/fern.git' + developerConnection = 'scm:git:git://github.com/path-parameters/fern.git' + url = 'https://github.com/path-parameters/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-model/path-parameters/settings.gradle b/seed/java-model/path-parameters/settings.gradle new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/path-parameters/snippet-templates.json b/seed/java-model/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/path-parameters/snippet.json b/seed/java-model/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/core/DateTimeDeserializer.java b/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..5730ae25702 --- /dev/null +++ b/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/core/ObjectMappers.java b/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/core/ObjectMappers.java new file mode 100644 index 00000000000..21c31be1521 --- /dev/null +++ b/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/model/user/User.java b/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/model/user/User.java new file mode 100644 index 00000000000..b915154d230 --- /dev/null +++ b/seed/java-model/path-parameters/src/main/java/com/seed/pathParameters/model/user/User.java @@ -0,0 +1,126 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.model.user; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.pathParameters.core.ObjectMappers; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = User.Builder.class) +public final class User { + private final String name; + + private final List tags; + + private User(String name, List tags) { + this.name = name; + this.tags = tags; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("tags") + public List getTags() { + return tags; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + private boolean equalTo(User other) { + return name.equals(other.name) && tags.equals(other.tags); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.tags); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NameStage builder() { + return new Builder(); + } + + public interface NameStage { + _FinalStage name(String name); + + Builder from(User other); + } + + public interface _FinalStage { + User build(); + + _FinalStage tags(List tags); + + _FinalStage addTags(String tags); + + _FinalStage addAllTags(List tags); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements NameStage, _FinalStage { + private String name; + + private List tags = new ArrayList<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(User other) { + name(other.getName()); + tags(other.getTags()); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage addAllTags(List tags) { + this.tags.addAll(tags); + return this; + } + + @java.lang.Override + public _FinalStage addTags(String tags) { + this.tags.add(tags); + return this; + } + + @java.lang.Override + @JsonSetter(value = "tags", nulls = Nulls.SKIP) + public _FinalStage tags(List tags) { + this.tags.clear(); + this.tags.addAll(tags); + return this; + } + + @java.lang.Override + public User build() { + return new User(name, tags); + } + } +} diff --git a/seed/java-sdk/path-parameters/.github/workflows/ci.yml b/seed/java-sdk/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..8598a73092a --- /dev/null +++ b/seed/java-sdk/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/path-parameters/.gitignore b/seed/java-sdk/path-parameters/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-sdk/path-parameters/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/path-parameters/.mock/definition/api.yml b/seed/java-sdk/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/java-sdk/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/java-sdk/path-parameters/.mock/definition/user.yml b/seed/java-sdk/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/java-sdk/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/java-sdk/path-parameters/.mock/fern.config.json b/seed/java-sdk/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-sdk/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-sdk/path-parameters/.mock/generators.yml b/seed/java-sdk/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/java-sdk/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/java-sdk/path-parameters/build.gradle b/seed/java-sdk/path-parameters/build.gradle new file mode 100644 index 00000000000..83082dc2a38 --- /dev/null +++ b/seed/java-sdk/path-parameters/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:4.12.0' + api 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +spotless { + java { + palantirJavaFormat() + } +} + +java { + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'path-parameters' + version = '0.0.1' + from components.java + pom { + scm { + connection = 'scm:git:git://github.com/path-parameters/fern.git' + developerConnection = 'scm:git:git://github.com/path-parameters/fern.git' + url = 'https://github.com/path-parameters/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/path-parameters/sample-app/build.gradle b/seed/java-sdk/path-parameters/sample-app/build.gradle new file mode 100644 index 00000000000..4ee8f227b7a --- /dev/null +++ b/seed/java-sdk/path-parameters/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/path-parameters/sample-app/src/main/java/sample/App.java b/seed/java-sdk/path-parameters/sample-app/src/main/java/sample/App.java new file mode 100644 index 00000000000..72ed5716499 --- /dev/null +++ b/seed/java-sdk/path-parameters/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.pathParameters.SeedPathParametersClient + } +} diff --git a/seed/java-sdk/path-parameters/settings.gradle b/seed/java-sdk/path-parameters/settings.gradle new file mode 100644 index 00000000000..aed36fec10b --- /dev/null +++ b/seed/java-sdk/path-parameters/settings.gradle @@ -0,0 +1 @@ +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/path-parameters/snippet-templates.json b/seed/java-sdk/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/path-parameters/snippet.json b/seed/java-sdk/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/SeedPathParametersClient.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/SeedPathParametersClient.java new file mode 100644 index 00000000000..c61802131db --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/SeedPathParametersClient.java @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters; + +import com.seed.pathParameters.core.ClientOptions; +import com.seed.pathParameters.core.Suppliers; +import com.seed.pathParameters.resources.user.UserClient; +import java.util.function.Supplier; + +public class SeedPathParametersClient { + protected final ClientOptions clientOptions; + + protected final Supplier userClient; + + public SeedPathParametersClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.userClient = Suppliers.memoize(() -> new UserClient(clientOptions)); + } + + public UserClient user() { + return this.userClient.get(); + } + + public static SeedPathParametersClientBuilder builder() { + return new SeedPathParametersClientBuilder(); + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/SeedPathParametersClientBuilder.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/SeedPathParametersClientBuilder.java new file mode 100644 index 00000000000..b76bd286050 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/SeedPathParametersClientBuilder.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters; + +import com.seed.pathParameters.core.ClientOptions; +import com.seed.pathParameters.core.Environment; + +public final class SeedPathParametersClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private Environment environment; + + public SeedPathParametersClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + public SeedPathParametersClient build() { + clientOptionsBuilder.environment(this.environment); + return new SeedPathParametersClient(clientOptionsBuilder.build()); + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ClientOptions.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ClientOptions.java new file mode 100644 index 00000000000..c2449ee4dfe --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ClientOptions.java @@ -0,0 +1,103 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("X-Fern-Language", "JAVA"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public ClientOptions build() { + OkHttpClient okhttpClient = new OkHttpClient.Builder() + .addInterceptor(new RetryInterceptor(3)) + .build(); + return new ClientOptions(environment, headers, headerSuppliers, okhttpClient); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/DateTimeDeserializer.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..5730ae25702 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Environment.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Environment.java new file mode 100644 index 00000000000..f83973c2c76 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +public final class Environment { + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/FileStream.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/FileStream.java new file mode 100644 index 00000000000..a4d2ca64ba4 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/FileStream.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/InputStreamRequestBody.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/InputStreamRequestBody.java new file mode 100644 index 00000000000..c8bcc2dc0f1 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/InputStreamRequestBody.java @@ -0,0 +1,79 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okhttp3.internal.Util; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + Source source = null; + try { + source = Okio.source(inputStream); + sink.writeAll(source); + } finally { + Util.closeQuietly(Objects.requireNonNull(source)); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/MediaTypes.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/MediaTypes.java new file mode 100644 index 00000000000..ae9c828d514 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ObjectMappers.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ObjectMappers.java new file mode 100644 index 00000000000..21c31be1521 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/RequestOptions.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/RequestOptions.java new file mode 100644 index 00000000000..7296d948670 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/RequestOptions.java @@ -0,0 +1,58 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private RequestOptions(Optional timeout, TimeUnit timeoutTimeUnit) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public RequestOptions build() { + return new RequestOptions(timeout, timeoutTimeUnit); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ResponseBodyInputStream.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ResponseBodyInputStream.java new file mode 100644 index 00000000000..50f78009e79 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ResponseBodyReader.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ResponseBodyReader.java new file mode 100644 index 00000000000..21ccf6dd50e --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/RetryInterceptor.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/RetryInterceptor.java new file mode 100644 index 00000000000..c87c58b3c02 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/RetryInterceptor.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration ONE_SECOND = Duration.ofSeconds(1); + private final ExponentialBackoff backoff; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.backoff = new ExponentialBackoff(maxRetries); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + Optional nextBackoff = this.backoff.nextBackoff(); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = this.backoff.nextBackoff(); + } else { + return response; + } + } + + return response; + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 409 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff() { + retryNumber += 1; + if (retryNumber > maxNumRetries) { + return Optional.empty(); + } + + int upperBound = (int) Math.pow(2, retryNumber); + return Optional.of(ONE_SECOND.multipliedBy(random.nextInt(upperBound))); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/SeedPathParametersApiException.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/SeedPathParametersApiException.java new file mode 100644 index 00000000000..ce9e174a78b --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/SeedPathParametersApiException.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class SeedPathParametersApiException extends SeedPathParametersException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + public SeedPathParametersApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + @java.lang.Override + public String toString() { + return "SeedPathParametersApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + + ", body: " + body + "}"; + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/SeedPathParametersException.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/SeedPathParametersException.java new file mode 100644 index 00000000000..6c5fb16e9a5 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/SeedPathParametersException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class SeedPathParametersException extends RuntimeException { + public SeedPathParametersException(String message) { + super(message); + } + + public SeedPathParametersException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Stream.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Stream.java new file mode 100644 index 00000000000..6b211f09a2e --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Stream.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implmenets {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable { + /** + * The {@link Class} of the objects in the stream. + */ + private final Class valueType; + /** + * The {@link Scanner} used for reading from the input stream and blocking when neede during iteration. + */ + private final Scanner scanner; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.valueType = valueType; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + return new Iterator() { + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = ObjectMappers.JSON_MAPPER.readValue( + scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Removing elements from {@code Stream} is not supported. + * + * @throws UnsupportedOperationException Always, as removal is not supported. + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Suppliers.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Suppliers.java new file mode 100644 index 00000000000..58e7002651d --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/UserClient.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/UserClient.java new file mode 100644 index 00000000000..77e830cef27 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/UserClient.java @@ -0,0 +1,148 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.resources.user; + +import com.seed.pathParameters.core.ClientOptions; +import com.seed.pathParameters.core.ObjectMappers; +import com.seed.pathParameters.core.RequestOptions; +import com.seed.pathParameters.core.SeedPathParametersApiException; +import com.seed.pathParameters.core.SeedPathParametersException; +import com.seed.pathParameters.resources.user.requests.GetOrganizationUserRequest; +import com.seed.pathParameters.resources.user.requests.GetUsersRequest; +import com.seed.pathParameters.resources.user.types.User; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class UserClient { + protected final ClientOptions clientOptions; + + public UserClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public User getOrganization(String organizationId) { + return getOrganization(organizationId, null); + } + + public User getOrganization(String organizationId, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("user") + .addPathSegments("organizations") + .addPathSegment(organizationId) + .build(); + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), User.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedPathParametersApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedPathParametersException("Network error executing HTTP request", e); + } + } + + public User getUser(String userId) { + return getUser(userId, GetUsersRequest.builder().build()); + } + + public User getUser(String userId, GetUsersRequest request) { + return getUser(userId, request, null); + } + + public User getUser(String userId, GetUsersRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("user") + .addPathSegments("users") + .addPathSegment(userId) + .build(); + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), User.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedPathParametersApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedPathParametersException("Network error executing HTTP request", e); + } + } + + public User getOrganizationUser(String organizationId, String userId) { + return getOrganizationUser( + organizationId, userId, GetOrganizationUserRequest.builder().build()); + } + + public User getOrganizationUser(String organizationId, String userId, GetOrganizationUserRequest request) { + return getOrganizationUser(organizationId, userId, request, null); + } + + public User getOrganizationUser( + String organizationId, String userId, GetOrganizationUserRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("user") + .addPathSegments("organizations") + .addPathSegment(organizationId) + .addPathSegments("users") + .addPathSegment(userId) + .build(); + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), User.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedPathParametersApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedPathParametersException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/requests/GetOrganizationUserRequest.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/requests/GetOrganizationUserRequest.java new file mode 100644 index 00000000000..1a856ebb6b6 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/requests/GetOrganizationUserRequest.java @@ -0,0 +1,59 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.resources.user.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.pathParameters.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = GetOrganizationUserRequest.Builder.class) +public final class GetOrganizationUserRequest { + private final Map additionalProperties; + + private GetOrganizationUserRequest(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof GetOrganizationUserRequest; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(GetOrganizationUserRequest other) { + return this; + } + + public GetOrganizationUserRequest build() { + return new GetOrganizationUserRequest(additionalProperties); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/requests/GetUsersRequest.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/requests/GetUsersRequest.java new file mode 100644 index 00000000000..63db94203ba --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/requests/GetUsersRequest.java @@ -0,0 +1,59 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.resources.user.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.pathParameters.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = GetUsersRequest.Builder.class) +public final class GetUsersRequest { + private final Map additionalProperties; + + private GetUsersRequest(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof GetUsersRequest; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(GetUsersRequest other) { + return this; + } + + public GetUsersRequest build() { + return new GetUsersRequest(additionalProperties); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/types/User.java b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/types/User.java new file mode 100644 index 00000000000..0ddfee73971 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/main/java/com/seed/pathParameters/resources/user/types/User.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters.resources.user.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.pathParameters.core.ObjectMappers; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = User.Builder.class) +public final class User { + private final String name; + + private final List tags; + + private final Map additionalProperties; + + private User(String name, List tags, Map additionalProperties) { + this.name = name; + this.tags = tags; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("tags") + public List getTags() { + return tags; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(User other) { + return name.equals(other.name) && tags.equals(other.tags); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.tags); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NameStage builder() { + return new Builder(); + } + + public interface NameStage { + _FinalStage name(@NotNull String name); + + Builder from(User other); + } + + public interface _FinalStage { + User build(); + + _FinalStage tags(List tags); + + _FinalStage addTags(String tags); + + _FinalStage addAllTags(List tags); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements NameStage, _FinalStage { + private String name; + + private List tags = new ArrayList<>(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(User other) { + name(other.getName()); + tags(other.getTags()); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage addAllTags(List tags) { + this.tags.addAll(tags); + return this; + } + + @java.lang.Override + public _FinalStage addTags(String tags) { + this.tags.add(tags); + return this; + } + + @java.lang.Override + @JsonSetter(value = "tags", nulls = Nulls.SKIP) + public _FinalStage tags(List tags) { + this.tags.clear(); + this.tags.addAll(tags); + return this; + } + + @java.lang.Override + public User build() { + return new User(name, tags, additionalProperties); + } + } +} diff --git a/seed/java-sdk/path-parameters/src/test/java/com/seed/pathParameters/TestClient.java b/seed/java-sdk/path-parameters/src/test/java/com/seed/pathParameters/TestClient.java new file mode 100644 index 00000000000..8e25b161b29 --- /dev/null +++ b/seed/java-sdk/path-parameters/src/test/java/com/seed/pathParameters/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.pathParameters; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-spring/path-parameters/.mock/definition/api.yml b/seed/java-spring/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/java-spring/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/java-spring/path-parameters/.mock/definition/user.yml b/seed/java-spring/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/java-spring/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/java-spring/path-parameters/.mock/fern.config.json b/seed/java-spring/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-spring/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-spring/path-parameters/.mock/generators.yml b/seed/java-spring/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/java-spring/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/java-spring/path-parameters/core/APIException.java b/seed/java-spring/path-parameters/core/APIException.java new file mode 100644 index 00000000000..27289cf9b2e --- /dev/null +++ b/seed/java-spring/path-parameters/core/APIException.java @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import java.lang.Exception; + +public class APIException extends Exception { +} diff --git a/seed/java-spring/path-parameters/core/DateTimeDeserializer.java b/seed/java-spring/path-parameters/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..3d3174aec00 --- /dev/null +++ b/seed/java-spring/path-parameters/core/DateTimeDeserializer.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} \ No newline at end of file diff --git a/seed/java-spring/path-parameters/core/ObjectMappers.java b/seed/java-spring/path-parameters/core/ObjectMappers.java new file mode 100644 index 00000000000..e02822614a8 --- /dev/null +++ b/seed/java-spring/path-parameters/core/ObjectMappers.java @@ -0,0 +1,41 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() { + } + + public static String stringify(Object o) { + try { + return JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } + catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + } diff --git a/seed/java-spring/path-parameters/resources/user/UserService.java b/seed/java-spring/path-parameters/resources/user/UserService.java new file mode 100644 index 00000000000..2091c3fd321 --- /dev/null +++ b/seed/java-spring/path-parameters/resources/user/UserService.java @@ -0,0 +1,35 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user; + +import java.lang.String; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import resources.user.types.User; + +@RequestMapping( + path = "/user" +) +public interface UserService { + @GetMapping( + value = "/organizations/{organizationId}", + produces = "application/json" + ) + User getOrganization(@PathVariable("organizationId") String organizationId); + + @GetMapping( + value = "/users/{userId}", + produces = "application/json" + ) + User getUser(@PathVariable("userId") String userId); + + @GetMapping( + value = "/organizations/{organizationId}/users/{userId}", + produces = "application/json" + ) + User getOrganizationUser(@PathVariable("organizationId") String organizationId, + @PathVariable("userId") String userId); +} diff --git a/seed/java-spring/path-parameters/resources/user/types/User.java b/seed/java-spring/path-parameters/resources/user/types/User.java new file mode 100644 index 00000000000..6276c40bfef --- /dev/null +++ b/seed/java-spring/path-parameters/resources/user/types/User.java @@ -0,0 +1,138 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user.types; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = User.Builder.class +) +public final class User { + private final String name; + + private final List tags; + + private User(String name, List tags) { + this.name = name; + this.tags = tags; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("tags") + public List getTags() { + return tags; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + private boolean equalTo(User other) { + return name.equals(other.name) && tags.equals(other.tags); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.name, this.tags); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static NameStage builder() { + return new Builder(); + } + + public interface NameStage { + _FinalStage name(@NotNull String name); + + Builder from(User other); + } + + public interface _FinalStage { + User build(); + + _FinalStage tags(List tags); + + _FinalStage addTags(String tags); + + _FinalStage addAllTags(List tags); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements NameStage, _FinalStage { + private String name; + + private List tags = new ArrayList<>(); + + private Builder() { + } + + @java.lang.Override + public Builder from(User other) { + name(other.getName()); + tags(other.getTags()); + return this; + } + + @java.lang.Override + @JsonSetter("name") + public _FinalStage name(@NotNull String name) { + this.name = Objects.requireNonNull(name, "name must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage addAllTags(List tags) { + this.tags.addAll(tags); + return this; + } + + @java.lang.Override + public _FinalStage addTags(String tags) { + this.tags.add(tags); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "tags", + nulls = Nulls.SKIP + ) + public _FinalStage tags(List tags) { + this.tags.clear(); + this.tags.addAll(tags); + return this; + } + + @java.lang.Override + public User build() { + return new User(name, tags); + } + } +} diff --git a/seed/java-spring/path-parameters/snippet-templates.json b/seed/java-spring/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-spring/path-parameters/snippet.json b/seed/java-spring/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/openapi/path-parameters/.mock/definition/api.yml b/seed/openapi/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/openapi/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/openapi/path-parameters/.mock/definition/user.yml b/seed/openapi/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/openapi/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/openapi/path-parameters/.mock/fern.config.json b/seed/openapi/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/openapi/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/openapi/path-parameters/.mock/generators.yml b/seed/openapi/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/openapi/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/openapi/path-parameters/openapi.yml b/seed/openapi/path-parameters/openapi.yml new file mode 100644 index 00000000000..8412e2eb5ec --- /dev/null +++ b/seed/openapi/path-parameters/openapi.yml @@ -0,0 +1,80 @@ +openapi: 3.0.1 +info: + title: path-parameters + version: '' +paths: + /user/organizations/{organizationId}: + get: + operationId: user_getOrganization + tags: + - User + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/User' + /user/users/{userId}: + get: + operationId: user_getUser + tags: + - User + parameters: + - name: userId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/User' + /user/organizations/{organizationId}/users/{userId}: + get: + operationId: user_getOrganizationUser + tags: + - User + parameters: + - name: organizationId + in: path + required: true + schema: + type: string + - name: userId + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/User' +components: + schemas: + User: + title: User + type: object + properties: + name: + type: string + tags: + type: array + items: + type: string + required: + - name + - tags + securitySchemes: {} diff --git a/seed/openapi/path-parameters/snippet-templates.json b/seed/openapi/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/openapi/path-parameters/snippet.json b/seed/openapi/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/path-parameters/.github/workflows/ci.yml b/seed/php-model/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..258bf33a19f --- /dev/null +++ b/seed/php-model/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test \ No newline at end of file diff --git a/seed/php-model/path-parameters/.gitignore b/seed/php-model/path-parameters/.gitignore new file mode 100644 index 00000000000..f38efc46ade --- /dev/null +++ b/seed/php-model/path-parameters/.gitignore @@ -0,0 +1,4 @@ +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-model/path-parameters/.mock/definition/api.yml b/seed/php-model/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/php-model/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/php-model/path-parameters/.mock/definition/user.yml b/seed/php-model/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/php-model/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/php-model/path-parameters/.mock/fern.config.json b/seed/php-model/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/php-model/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/php-model/path-parameters/.mock/generators.yml b/seed/php-model/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/php-model/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/php-model/path-parameters/composer.json b/seed/php-model/path-parameters/composer.json new file mode 100644 index 00000000000..5c96c0056e0 --- /dev/null +++ b/seed/php-model/path-parameters/composer.json @@ -0,0 +1,40 @@ + +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "\\Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src tests" + } +} diff --git a/seed/php-model/path-parameters/phpstan.neon b/seed/php-model/path-parameters/phpstan.neon new file mode 100644 index 00000000000..29a11a92a19 --- /dev/null +++ b/seed/php-model/path-parameters/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-model/path-parameters/phpunit.xml b/seed/php-model/path-parameters/phpunit.xml new file mode 100644 index 00000000000..54630a51163 --- /dev/null +++ b/seed/php-model/path-parameters/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-model/path-parameters/snippet-templates.json b/seed/php-model/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/path-parameters/snippet.json b/seed/php-model/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/path-parameters/src/Core/Json/JsonDecoder.php b/seed/php-model/path-parameters/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-model/path-parameters/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/path-parameters/src/Core/Json/JsonDeserializer.php b/seed/php-model/path-parameters/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..5f0ca2d7ed0 --- /dev/null +++ b/seed/php-model/path-parameters/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement JsonSerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, JsonSerializableType::class)) { + throw new JsonException("$type is not a subclass of JsonSerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/path-parameters/src/Core/Json/JsonEncoder.php b/seed/php-model/path-parameters/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/path-parameters/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === Date::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle Date annotation + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === Date::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle Array annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/path-parameters/src/Core/Json/JsonSerializer.php b/seed/php-model/path-parameters/src/Core/Json/JsonSerializer.php new file mode 100644 index 00000000000..7dd6fe517af --- /dev/null +++ b/seed/php-model/path-parameters/src/Core/Json/JsonSerializer.php @@ -0,0 +1,192 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/path-parameters/src/Core/Json/Utils.php b/seed/php-model/path-parameters/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/path-parameters/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/path-parameters/src/Core/Types/ArrayType.php b/seed/php-model/path-parameters/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/path-parameters/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/path-parameters/src/Core/Types/Constant.php b/seed/php-model/path-parameters/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/path-parameters/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/path-parameters/src/User/User.php b/seed/php-model/path-parameters/src/User/User.php new file mode 100644 index 00000000000..4cd2ac6d742 --- /dev/null +++ b/seed/php-model/path-parameters/src/User/User.php @@ -0,0 +1,35 @@ + $tags + */ + #[JsonProperty('tags'), ArrayType(['string'])] + public array $tags; + + /** + * @param array{ + * name: string, + * tags: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->tags = $values['tags']; + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/DateArrayTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/DateArrayTest.php new file mode 100644 index 00000000000..a72cfdbdd22 --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/DateArrayTest.php @@ -0,0 +1,54 @@ +dates = $values['dates']; + } +} + +class DateArrayTest extends TestCase +{ + public function testDateTimeInArrays(): void + { + $expectedJson = json_encode( + [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ], + JSON_THROW_ON_ERROR + ); + + $object = DateArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/EmptyArrayTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/EmptyArrayTest.php new file mode 100644 index 00000000000..d243a08916d --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/EmptyArrayTest.php @@ -0,0 +1,71 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArrayTest extends TestCase +{ + public function testEmptyArray(): void + { + $expectedJson = json_encode( + [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ], + JSON_THROW_ON_ERROR + ); + + $object = EmptyArray::fromJson($expectedJson); + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..bf83d5b8ab0 --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends JsonSerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $actualJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $actualJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/ExhaustiveTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/ExhaustiveTest.php new file mode 100644 index 00000000000..f542d6a535d --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/ExhaustiveTest.php @@ -0,0 +1,197 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class Type extends JsonSerializableType +{ + /** + * @var Nested nestedType + */ + #[JsonProperty('nested_type')] + public Nested $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[Date(Date::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[Date(Date::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(Nested::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: Nested, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class ExhaustiveTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in Type. + */ + public function testExhaustive(): void + { + $expectedJson = json_encode( + [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // Omit 'nullable_property' to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> + ], + JSON_THROW_ON_ERROR + ); + + $object = Type::fromJson($expectedJson); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/InvalidTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/InvalidTest.php new file mode 100644 index 00000000000..7d1d79406a5 --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/InvalidTest.php @@ -0,0 +1,42 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTest extends TestCase +{ + public function testInvalidJsonThrowsException(): void + { + $this->expectException(\TypeError::class); + $json = json_encode( + [ + 'integer_property' => 'not_an_integer' + ], + JSON_THROW_ON_ERROR + ); + Invalid::fromJson($json); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/NestedUnionArrayTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/NestedUnionArrayTest.php new file mode 100644 index 00000000000..0fcdd06667e --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/NestedUnionArrayTest.php @@ -0,0 +1,89 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArray extends JsonSerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTest extends TestCase +{ + public function testNestedUnionArray(): void + { + $expectedJson = json_encode( + [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ], + JSON_THROW_ON_ERROR + ); + + $object = NestedUnionArray::fromJson($expectedJson); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/NullPropertyTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/NullPropertyTest.php new file mode 100644 index 00000000000..ce20a244282 --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/NullPropertyTest.php @@ -0,0 +1,53 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullProperty( + [ + "nonNullProperty" => "Test String", + "nullProperty" => null + ] + ); + + $serialized = $object->jsonSerialize(); + $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); + $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/NullableArrayTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/NullableArrayTest.php new file mode 100644 index 00000000000..fe0f19de6b1 --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/NullableArrayTest.php @@ -0,0 +1,49 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTest extends TestCase +{ + public function testNullableArray(): void + { + $expectedJson = json_encode( + [ + 'nullable_string_array' => ['one', null, 'three'] + ], + JSON_THROW_ON_ERROR + ); + + $object = NullableArray::fromJson($expectedJson); + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/ScalarTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/ScalarTest.php new file mode 100644 index 00000000000..604b7c0b959 --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/ScalarTest.php @@ -0,0 +1,116 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + $expectedJson = json_encode( + [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats + ], + JSON_THROW_ON_ERROR + ); + + $object = Scalar::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..837f239115f --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,60 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $expectedJson = json_encode( + [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ], + JSON_THROW_ON_ERROR + ); + + $object = TypeWithTrait::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/UnionArrayTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/UnionArrayTest.php new file mode 100644 index 00000000000..09933d2321d --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/UnionArrayTest.php @@ -0,0 +1,57 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class UnionArrayTest extends TestCase +{ + public function testUnionArray(): void + { + $expectedJson = json_encode( + [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/path-parameters/tests/Seed/Core/Json/UnionPropertyTest.php b/seed/php-model/path-parameters/tests/Seed/Core/Json/UnionPropertyTest.php new file mode 100644 index 00000000000..3119baace62 --- /dev/null +++ b/seed/php-model/path-parameters/tests/Seed/Core/Json/UnionPropertyTest.php @@ -0,0 +1,115 @@ + 'integer'], UnionProperty::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionProperty + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => [1 => 100, 2 => 200] + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => new UnionProperty( + [ + 'complexUnion' => 'Nested String' + ] + ) + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $expectedJson = json_encode( + [], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => 42 + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => 'Some String' + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/path-parameters/.github/workflows/ci.yml b/seed/php-sdk/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..258bf33a19f --- /dev/null +++ b/seed/php-sdk/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test \ No newline at end of file diff --git a/seed/php-sdk/path-parameters/.gitignore b/seed/php-sdk/path-parameters/.gitignore new file mode 100644 index 00000000000..f38efc46ade --- /dev/null +++ b/seed/php-sdk/path-parameters/.gitignore @@ -0,0 +1,4 @@ +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-sdk/path-parameters/.mock/definition/api.yml b/seed/php-sdk/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/php-sdk/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/php-sdk/path-parameters/.mock/definition/user.yml b/seed/php-sdk/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/php-sdk/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/php-sdk/path-parameters/.mock/fern.config.json b/seed/php-sdk/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/php-sdk/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/php-sdk/path-parameters/.mock/generators.yml b/seed/php-sdk/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/php-sdk/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/php-sdk/path-parameters/composer.json b/seed/php-sdk/path-parameters/composer.json new file mode 100644 index 00000000000..5c96c0056e0 --- /dev/null +++ b/seed/php-sdk/path-parameters/composer.json @@ -0,0 +1,40 @@ + +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "\\Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src tests" + } +} diff --git a/seed/php-sdk/path-parameters/phpstan.neon b/seed/php-sdk/path-parameters/phpstan.neon new file mode 100644 index 00000000000..29a11a92a19 --- /dev/null +++ b/seed/php-sdk/path-parameters/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-sdk/path-parameters/phpunit.xml b/seed/php-sdk/path-parameters/phpunit.xml new file mode 100644 index 00000000000..54630a51163 --- /dev/null +++ b/seed/php-sdk/path-parameters/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-sdk/path-parameters/snippet-templates.json b/seed/php-sdk/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-sdk/path-parameters/snippet.json b/seed/php-sdk/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-sdk/path-parameters/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/path-parameters/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Client/HttpMethod.php b/seed/php-sdk/path-parameters/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + MultipartApiRequest::class => array_merge( + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + MultipartApiRequest::class => $request->body != null ? new MultipartStream($request->body->toArray()) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Json/JsonApiRequest.php b/seed/php-sdk/path-parameters/src/Core/Json/JsonApiRequest.php new file mode 100644 index 00000000000..8fdf493606e --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Json/JsonApiRequest.php @@ -0,0 +1,28 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Json/JsonDecoder.php b/seed/php-sdk/path-parameters/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/path-parameters/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..5f0ca2d7ed0 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement JsonSerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, JsonSerializableType::class)) { + throw new JsonException("$type is not a subclass of JsonSerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Json/JsonEncoder.php b/seed/php-sdk/path-parameters/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === Date::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle Date annotation + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === Date::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle Array annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Json/JsonSerializer.php b/seed/php-sdk/path-parameters/src/Core/Json/JsonSerializer.php new file mode 100644 index 00000000000..7dd6fe517af --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Json/JsonSerializer.php @@ -0,0 +1,192 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Json/Utils.php b/seed/php-sdk/path-parameters/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartApiRequest.php b/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartApiRequest.php new file mode 100644 index 00000000000..7760366456c --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartApiRequest.php @@ -0,0 +1,28 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param ?MultipartFormData $body The multipart form data for the request (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly ?MultipartFormData $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartFormData.php b/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartFormData.php new file mode 100644 index 00000000000..33bb67b05bd --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartFormData.php @@ -0,0 +1,61 @@ + + */ + private array $parts = []; + + /** + * Adds a new part to the multipart form data. + * + * @param string $name + * @param string|int|bool|float|StreamInterface $value + * @param ?string $contentType + */ + public function add( + string $name, + string|int|bool|float|StreamInterface $value, + ?string $contentType = null, + ): void { + $headers = $contentType != null ? ['Content-Type' => $contentType] : null; + self::addPart( + new MultipartFormDataPart( + name: $name, + value: $value, + headers: $headers, + ) + ); + } + + /** + * Adds a new part to the multipart form data. + * + * @param MultipartFormDataPart $part + */ + public function addPart(MultipartFormDataPart $part): void + { + $this->parts[] = $part; + } + + /** + * Converts the multipart form data into an array suitable + * for Guzzle's multipart form data. + * + * @return array + * }> + */ + public function toArray(): array + { + return array_map(fn ($part) => $part->toArray(), $this->parts); + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartFormDataPart.php b/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartFormDataPart.php new file mode 100644 index 00000000000..c158903d84f --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Multipart/MultipartFormDataPart.php @@ -0,0 +1,76 @@ + + */ + private ?array $headers; + + /** + * @param string $name + * @param string|bool|float|int|StreamInterface $value + * @param ?string $filename + * @param ?array $headers + */ + public function __construct( + string $name, + string|bool|float|int|StreamInterface $value, + ?string $filename = null, + ?array $headers = null + ) { + $this->name = $name; + $this->contents = Utils::streamFor($value); + $this->filename = $filename; + $this->headers = $headers; + } + + /** + * Converts the multipart form data part into an array suitable + * for Guzzle's multipart form data. + * + * @return array{ + * name: string, + * contents: StreamInterface, + * filename?: string, + * headers?: array + * } + */ + public function toArray(): array + { + $formData = [ + 'name' => $this->name, + 'contents' => $this->contents, + ]; + + if ($this->filename != null) { + $formData['filename'] = $this->filename; + } + + if ($this->headers != null) { + $formData['headers'] = $this->headers; + } + + return $formData; + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Types/ArrayType.php b/seed/php-sdk/path-parameters/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/path-parameters/src/Core/Types/Constant.php b/seed/php-sdk/path-parameters/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/path-parameters/src/Exceptions/SeedApiException.php b/seed/php-sdk/path-parameters/src/Exceptions/SeedApiException.php new file mode 100644 index 00000000000..41a85392b70 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Exceptions/SeedApiException.php @@ -0,0 +1,53 @@ +body = $body; + parent::__construct($message, $statusCode, $previous); + } + + /** + * Returns the body of the response that triggered the exception. + * + * @return mixed + */ + public function getBody(): mixed + { + return $this->body; + } + + /** + * @return string + */ + public function __toString(): string + { + if (empty($this->body)) { + return "$this->message; Status Code: $this->code\n"; + } + return "$this->message; Status Code: $this->code; Body: " . $this->body . "\n"; + } +} diff --git a/seed/php-sdk/path-parameters/src/Exceptions/SeedException.php b/seed/php-sdk/path-parameters/src/Exceptions/SeedException.php new file mode 100644 index 00000000000..45703527673 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Exceptions/SeedException.php @@ -0,0 +1,12 @@ +, + * } $options + */ + private ?array $options; + + /** + * @var RawClient $client + */ + private RawClient $client; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + ?array $options = null, + ) { + $defaultHeaders = [ + 'X-Fern-Language' => 'PHP', + 'X-Fern-SDK-Name' => 'Seed', + 'X-Fern-SDK-Version' => '0.0.1', + ]; + + $this->options = $options ?? []; + $this->options['headers'] = array_merge( + $defaultHeaders, + $this->options['headers'] ?? [], + ); + + $this->client = new RawClient( + options: $this->options, + ); + + $this->user = new UserClient($this->client); + } +} diff --git a/seed/php-sdk/path-parameters/src/User/Requests/GetOrganizationUserRequest.php b/seed/php-sdk/path-parameters/src/User/Requests/GetOrganizationUserRequest.php new file mode 100644 index 00000000000..0bc67b50d15 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/User/Requests/GetOrganizationUserRequest.php @@ -0,0 +1,9 @@ + $tags + */ + #[JsonProperty('tags'), ArrayType(['string'])] + public array $tags; + + /** + * @param array{ + * name: string, + * tags: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->name = $values['name']; + $this->tags = $values['tags']; + } +} diff --git a/seed/php-sdk/path-parameters/src/User/UserClient.php b/seed/php-sdk/path-parameters/src/User/UserClient.php new file mode 100644 index 00000000000..be29aafbd7c --- /dev/null +++ b/seed/php-sdk/path-parameters/src/User/UserClient.php @@ -0,0 +1,142 @@ +client = $client; + } + + /** + * @param string $organizationId + * @param ?array{ + * baseUrl?: string, + * } $options + * @return User + * @throws SeedException + * @throws SeedApiException + */ + public function getOrganization(string $organizationId, ?array $options = null): User + { + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', + path: "/user/organizations/$organizationId", + method: HttpMethod::GET, + ), + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + return User::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param string $userId + * @param GetUsersRequest $request + * @param ?array{ + * baseUrl?: string, + * } $options + * @return User + * @throws SeedException + * @throws SeedApiException + */ + public function getUser(string $userId, GetUsersRequest $request, ?array $options = null): User + { + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', + path: "/user/users/$userId", + method: HttpMethod::GET, + ), + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + return User::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } + + /** + * @param string $organizationId + * @param string $userId + * @param GetOrganizationUserRequest $request + * @param ?array{ + * baseUrl?: string, + * } $options + * @return User + * @throws SeedException + * @throws SeedApiException + */ + public function getOrganizationUser(string $organizationId, string $userId, GetOrganizationUserRequest $request, ?array $options = null): User + { + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', + path: "/user/organizations/$organizationId/users/$userId", + method: HttpMethod::GET, + ), + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + return User::fromJson($json); + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } +} diff --git a/seed/php-sdk/path-parameters/src/Utils/File.php b/seed/php-sdk/path-parameters/src/Utils/File.php new file mode 100644 index 00000000000..753748138d4 --- /dev/null +++ b/seed/php-sdk/path-parameters/src/Utils/File.php @@ -0,0 +1,125 @@ +filename = $filename; + $this->contentType = $contentType; + $this->stream = $stream; + } + + /** + * Creates a File instance from a filepath. + * + * @param string $filepath + * @param ?string $filename + * @param ?string $contentType + * @return File + * @throws Exception + */ + public static function createFromFilepath( + string $filepath, + ?string $filename = null, + ?string $contentType = null, + ): File { + $resource = fopen($filepath, 'r'); + if (!$resource) { + throw new Exception("Unable to open file $filepath"); + } + $stream = Utils::streamFor($resource); + if (!$stream->isReadable()) { + throw new Exception("File $filepath is not readable"); + } + return new self( + stream: $stream, + filename: $filename ?? basename($filepath), + contentType: $contentType, + ); + } + + /** + * Creates a File instance from a string. + * + * @param string $content + * @param ?string $filename + * @param ?string $contentType + * @return File + */ + public static function createFromString( + string $content, + ?string $filename, + ?string $contentType = null, + ): File { + return new self( + stream: Utils::streamFor($content), + filename: $filename, + contentType: $contentType, + ); + } + + /** + * Maps this File into a multipart form data part. + * + * @param string $name The name of the mutlipart form data part. + * @param ?string $contentType Overrides the Content-Type associated with the file, if any. + * @return MultipartFormDataPart + */ + public function toMultipartFormDataPart(string $name, ?string $contentType = null): MultipartFormDataPart + { + $contentType ??= $this->contentType; + $headers = $contentType != null + ? ['Content-Type' => $contentType] + : null; + + return new MultipartFormDataPart( + name: $name, + value: $this->stream, + filename: $this->filename, + headers: $headers, + ); + } + + /** + * Closes the file stream. + */ + public function close(): void + { + $this->stream->close(); + } + + /** + * Destructor to ensure stream is closed. + */ + public function __destruct() + { + $this->close(); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/DateArrayTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/DateArrayTest.php new file mode 100644 index 00000000000..a72cfdbdd22 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/DateArrayTest.php @@ -0,0 +1,54 @@ +dates = $values['dates']; + } +} + +class DateArrayTest extends TestCase +{ + public function testDateTimeInArrays(): void + { + $expectedJson = json_encode( + [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ], + JSON_THROW_ON_ERROR + ); + + $object = DateArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/EmptyArrayTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/EmptyArrayTest.php new file mode 100644 index 00000000000..d243a08916d --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/EmptyArrayTest.php @@ -0,0 +1,71 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArrayTest extends TestCase +{ + public function testEmptyArray(): void + { + $expectedJson = json_encode( + [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ], + JSON_THROW_ON_ERROR + ); + + $object = EmptyArray::fromJson($expectedJson); + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..bf83d5b8ab0 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends JsonSerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $actualJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $actualJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/ExhaustiveTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/ExhaustiveTest.php new file mode 100644 index 00000000000..f542d6a535d --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/ExhaustiveTest.php @@ -0,0 +1,197 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class Type extends JsonSerializableType +{ + /** + * @var Nested nestedType + */ + #[JsonProperty('nested_type')] + public Nested $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[Date(Date::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[Date(Date::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(Nested::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: Nested, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class ExhaustiveTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in Type. + */ + public function testExhaustive(): void + { + $expectedJson = json_encode( + [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // Omit 'nullable_property' to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> + ], + JSON_THROW_ON_ERROR + ); + + $object = Type::fromJson($expectedJson); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/InvalidTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/InvalidTest.php new file mode 100644 index 00000000000..7d1d79406a5 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/InvalidTest.php @@ -0,0 +1,42 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTest extends TestCase +{ + public function testInvalidJsonThrowsException(): void + { + $this->expectException(\TypeError::class); + $json = json_encode( + [ + 'integer_property' => 'not_an_integer' + ], + JSON_THROW_ON_ERROR + ); + Invalid::fromJson($json); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NestedUnionArrayTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NestedUnionArrayTest.php new file mode 100644 index 00000000000..0fcdd06667e --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NestedUnionArrayTest.php @@ -0,0 +1,89 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArray extends JsonSerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTest extends TestCase +{ + public function testNestedUnionArray(): void + { + $expectedJson = json_encode( + [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ], + JSON_THROW_ON_ERROR + ); + + $object = NestedUnionArray::fromJson($expectedJson); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NullPropertyTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NullPropertyTest.php new file mode 100644 index 00000000000..ce20a244282 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NullPropertyTest.php @@ -0,0 +1,53 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullProperty( + [ + "nonNullProperty" => "Test String", + "nullProperty" => null + ] + ); + + $serialized = $object->jsonSerialize(); + $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); + $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NullableArrayTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NullableArrayTest.php new file mode 100644 index 00000000000..fe0f19de6b1 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/NullableArrayTest.php @@ -0,0 +1,49 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTest extends TestCase +{ + public function testNullableArray(): void + { + $expectedJson = json_encode( + [ + 'nullable_string_array' => ['one', null, 'three'] + ], + JSON_THROW_ON_ERROR + ); + + $object = NullableArray::fromJson($expectedJson); + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/ScalarTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/ScalarTest.php new file mode 100644 index 00000000000..604b7c0b959 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/ScalarTest.php @@ -0,0 +1,116 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + $expectedJson = json_encode( + [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats + ], + JSON_THROW_ON_ERROR + ); + + $object = Scalar::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/TraitTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..837f239115f --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,60 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $expectedJson = json_encode( + [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ], + JSON_THROW_ON_ERROR + ); + + $object = TypeWithTrait::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/UnionArrayTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/UnionArrayTest.php new file mode 100644 index 00000000000..09933d2321d --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/UnionArrayTest.php @@ -0,0 +1,57 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class UnionArrayTest extends TestCase +{ + public function testUnionArray(): void + { + $expectedJson = json_encode( + [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/path-parameters/tests/Seed/Core/Json/UnionPropertyTest.php b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/UnionPropertyTest.php new file mode 100644 index 00000000000..3119baace62 --- /dev/null +++ b/seed/php-sdk/path-parameters/tests/Seed/Core/Json/UnionPropertyTest.php @@ -0,0 +1,115 @@ + 'integer'], UnionProperty::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionProperty + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => [1 => 100, 2 => 200] + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => new UnionProperty( + [ + 'complexUnion' => 'Nested String' + ] + ) + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $expectedJson = json_encode( + [], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => 42 + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => 'Some String' + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/postman/path-parameters/.mock/definition/api.yml b/seed/postman/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/postman/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/postman/path-parameters/.mock/definition/user.yml b/seed/postman/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/postman/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/postman/path-parameters/.mock/fern.config.json b/seed/postman/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/postman/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/postman/path-parameters/.mock/generators.yml b/seed/postman/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/postman/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/postman/path-parameters/collection.json b/seed/postman/path-parameters/collection.json new file mode 100644 index 00000000000..4c0ec395158 --- /dev/null +++ b/seed/postman/path-parameters/collection.json @@ -0,0 +1,272 @@ +{ + "info": { + "name": "Path Parameters", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": null + }, + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "string" + } + ], + "auth": null, + "item": [ + { + "_type": "container", + "description": null, + "name": "User", + "item": [ + { + "_type": "endpoint", + "name": "Get Organization", + "request": { + "description": null, + "url": { + "raw": "{{baseUrl}}/user/organizations/:organizationId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "user", + "organizations", + ":organizationId" + ], + "query": [], + "variable": [ + { + "key": "organizationId", + "description": null, + "value": "organizationId" + } + ] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "GET", + "auth": null, + "body": null + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "originalRequest": { + "description": null, + "url": { + "raw": "{{baseUrl}}/user/organizations/:organizationId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "user", + "organizations", + ":organizationId" + ], + "query": [], + "variable": [ + { + "key": "organizationId", + "description": null, + "value": "organizationId" + } + ] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "GET", + "auth": null, + "body": null + }, + "description": null, + "body": "{\n \"name\": \"name\",\n \"tags\": [\n \"tags\",\n \"tags\"\n ]\n}", + "_postman_previewlanguage": "json" + } + ] + }, + { + "_type": "endpoint", + "name": "Get User", + "request": { + "description": null, + "url": { + "raw": "{{baseUrl}}/user/users/:userId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "user", + "users", + ":userId" + ], + "query": [], + "variable": [ + { + "key": "userId", + "description": null, + "value": "userId" + } + ] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "GET", + "auth": null, + "body": null + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "originalRequest": { + "description": null, + "url": { + "raw": "{{baseUrl}}/user/users/:userId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "user", + "users", + ":userId" + ], + "query": [], + "variable": [ + { + "key": "userId", + "description": null, + "value": "userId" + } + ] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "GET", + "auth": null, + "body": null + }, + "description": null, + "body": "{\n \"name\": \"name\",\n \"tags\": [\n \"tags\",\n \"tags\"\n ]\n}", + "_postman_previewlanguage": "json" + } + ] + }, + { + "_type": "endpoint", + "name": "Get Organization User", + "request": { + "description": null, + "url": { + "raw": "{{baseUrl}}/user/organizations/:organizationId/users/:userId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "user", + "organizations", + ":organizationId", + "users", + ":userId" + ], + "query": [], + "variable": [ + { + "key": "organizationId", + "description": null, + "value": "organizationId" + }, + { + "key": "userId", + "description": null, + "value": "userId" + } + ] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "GET", + "auth": null, + "body": null + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "originalRequest": { + "description": null, + "url": { + "raw": "{{baseUrl}}/user/organizations/:organizationId/users/:userId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "user", + "organizations", + ":organizationId", + "users", + ":userId" + ], + "query": [], + "variable": [ + { + "key": "organizationId", + "description": null, + "value": "organizationId" + }, + { + "key": "userId", + "description": null, + "value": "userId" + } + ] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "GET", + "auth": null, + "body": null + }, + "description": null, + "body": "{\n \"name\": \"name\",\n \"tags\": [\n \"tags\",\n \"tags\"\n ]\n}", + "_postman_previewlanguage": "json" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/seed/postman/path-parameters/snippet-templates.json b/seed/postman/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/postman/path-parameters/snippet.json b/seed/postman/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic-v2/path-parameters/.mock/definition/api.yml b/seed/pydantic-v2/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/pydantic-v2/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/pydantic-v2/path-parameters/.mock/definition/user.yml b/seed/pydantic-v2/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/pydantic-v2/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/pydantic-v2/path-parameters/.mock/fern.config.json b/seed/pydantic-v2/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/pydantic-v2/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/pydantic-v2/path-parameters/.mock/generators.yml b/seed/pydantic-v2/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/pydantic-v2/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/pydantic-v2/path-parameters/resources/user/types/user.py b/seed/pydantic-v2/path-parameters/resources/user/types/user.py new file mode 100644 index 00000000000..c6f06633616 --- /dev/null +++ b/seed/pydantic-v2/path-parameters/resources/user/types/user.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel +from typing import List +from dt import datetime +from core.datetime_utils import serialize_datetime + + +class User(BaseModel): + name: str + tags: List[str] + + class Config: + frozen = True + smart_union = True + json_encoders = {datetime: serialize_datetime} diff --git a/seed/pydantic-v2/path-parameters/snippet-templates.json b/seed/pydantic-v2/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic-v2/path-parameters/snippet.json b/seed/pydantic-v2/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/path-parameters/.github/workflows/ci.yml b/seed/pydantic/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..b204fa604e2 --- /dev/null +++ b/seed/pydantic/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] +jobs: + compile: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest -rP . + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$" --password "$" + env: + : ${{ secrets. }} + : ${{ secrets. }} diff --git a/seed/pydantic/path-parameters/.gitignore b/seed/pydantic/path-parameters/.gitignore new file mode 100644 index 00000000000..0da665feeef --- /dev/null +++ b/seed/pydantic/path-parameters/.gitignore @@ -0,0 +1,5 @@ +dist/ +.mypy_cache/ +__pycache__/ +poetry.toml +.ruff_cache/ diff --git a/seed/pydantic/path-parameters/.mock/definition/api.yml b/seed/pydantic/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/pydantic/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/pydantic/path-parameters/.mock/definition/user.yml b/seed/pydantic/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/pydantic/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/pydantic/path-parameters/.mock/fern.config.json b/seed/pydantic/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/pydantic/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/pydantic/path-parameters/.mock/generators.yml b/seed/pydantic/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/pydantic/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/pydantic/path-parameters/README.md b/seed/pydantic/path-parameters/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/path-parameters/pyproject.toml b/seed/pydantic/path-parameters/pyproject.toml new file mode 100644 index 00000000000..859f03ec927 --- /dev/null +++ b/seed/pydantic/path-parameters/pyproject.toml @@ -0,0 +1,59 @@ +[tool.poetry] +name = "fern_path-parameters" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed/path_parameters", from = "src"} +] + +[project.urls] +Repository = 'https://github.com/path-parameters/fern' + +[tool.poetry.dependencies] +python = "^3.8" +pydantic = ">= 1.9.2" +pydantic-core = "^2.18.2" + +[tool.poetry.dev-dependencies] +mypy = "1.0.1" +pytest = "^7.4.0" +pytest-asyncio = "^0.23.5" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" +ruff = "^0.5.6" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.ruff] +line-length = 120 + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/pydantic/path-parameters/snippet-templates.json b/seed/pydantic/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/path-parameters/snippet.json b/seed/pydantic/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/__init__.py b/seed/pydantic/path-parameters/src/seed/path_parameters/__init__.py new file mode 100644 index 00000000000..03c422787a2 --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .resources import User, user + +__all__ = ["User", "user"] diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/core/__init__.py b/seed/pydantic/path-parameters/src/seed/path_parameters/core/__init__.py new file mode 100644 index 00000000000..9c7cd65aa25 --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/core/__init__.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .serialization import FieldMetadata + +__all__ = [ + "FieldMetadata", + "IS_PYDANTIC_V2", + "UniversalBaseModel", + "UniversalRootModel", + "parse_obj_as", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/core/datetime_utils.py b/seed/pydantic/path-parameters/src/seed/path_parameters/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/core/pydantic_utilities.py b/seed/pydantic/path-parameters/src/seed/path_parameters/core/pydantic_utilities.py new file mode 100644 index 00000000000..bbe1de41431 --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/core/pydantic_utilities.py @@ -0,0 +1,265 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict + +import typing_extensions + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + get_origin as get_origin, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_union as is_union, + ) + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + # Allow fields begining with `model_` to be used in the model + protected_namespaces = () + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multi-plexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields: typing.Any = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + return super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + +def _union_list_of_pydantic_dicts( + source: typing.List[typing.Any], destination: typing.List[typing.Any] +) -> typing.List[typing.Any]: + converted_list: typing.List[typing.Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] # type: ignore + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel: typing_extensions.TypeAlias = V2RootModel # type: ignore +else: + UniversalRootModel: typing_extensions.TypeAlias = UniversalBaseModel # type: ignore + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...]] = ( + defaultdict(tuple) + ) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator( + pre: bool = False, +) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return pydantic.model_validator(mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + return pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + return pydantic.validator(field_name, pre=pre)(func) # type: ignore # Pydantic v1 + + return decorator + + +PydanticField = typing.Union[ModelField, pydantic.fields.FieldInfo] + + +def _get_model_fields( + model: typing.Type["Model"], +) -> typing.Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return model.model_fields # type: ignore # Pydantic v2 + else: + return model.__fields__ # type: ignore # Pydantic v1 + + +def _get_field_default(field: PydanticField) -> typing.Any: + try: + value = field.get_default() # type: ignore # Pydantic < v1.10.15 + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/core/serialization.py b/seed/pydantic/path-parameters/src/seed/path_parameters/core/serialization.py new file mode 100644 index 00000000000..cb5dcbf93a9 --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/core/serialization.py @@ -0,0 +1,272 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import typing_extensions + +import pydantic + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/py.typed b/seed/pydantic/path-parameters/src/seed/path_parameters/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/resources/__init__.py b/seed/pydantic/path-parameters/src/seed/path_parameters/resources/__init__.py new file mode 100644 index 00000000000..26dc972c93a --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/resources/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from . import user +from .user import User + +__all__ = ["User", "user"] diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/resources/user/__init__.py b/seed/pydantic/path-parameters/src/seed/path_parameters/resources/user/__init__.py new file mode 100644 index 00000000000..b22b663beed --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/resources/user/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .user import User + +__all__ = ["User"] diff --git a/seed/pydantic/path-parameters/src/seed/path_parameters/resources/user/user.py b/seed/pydantic/path-parameters/src/seed/path_parameters/resources/user/user.py new file mode 100644 index 00000000000..1fdac8e7ecc --- /dev/null +++ b/seed/pydantic/path-parameters/src/seed/path_parameters/resources/user/user.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from ...core.pydantic_utilities import UniversalBaseModel +import typing +from ...core.pydantic_utilities import IS_PYDANTIC_V2 +import pydantic + + +class User(UniversalBaseModel): + name: str + tags: typing.List[str] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/path-parameters/tests/custom/test_client.py b/seed/pydantic/path-parameters/tests/custom/test_client.py new file mode 100644 index 00000000000..73f811f5ede --- /dev/null +++ b/seed/pydantic/path-parameters/tests/custom/test_client.py @@ -0,0 +1,7 @@ +import pytest + + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True == True diff --git a/seed/python-sdk/path-parameters/.github/workflows/ci.yml b/seed/python-sdk/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..b7316b8cab7 --- /dev/null +++ b/seed/python-sdk/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: ci + +on: [push] +jobs: + compile: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Install Fern + run: npm install -g fern-api + - name: Test + run: fern test --command "poetry run pytest -rP ." + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$" --password "$" + env: + : ${{ secrets. }} + : ${{ secrets. }} diff --git a/seed/python-sdk/path-parameters/.gitignore b/seed/python-sdk/path-parameters/.gitignore new file mode 100644 index 00000000000..0da665feeef --- /dev/null +++ b/seed/python-sdk/path-parameters/.gitignore @@ -0,0 +1,5 @@ +dist/ +.mypy_cache/ +__pycache__/ +poetry.toml +.ruff_cache/ diff --git a/seed/python-sdk/path-parameters/.mock/definition/api.yml b/seed/python-sdk/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/python-sdk/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/python-sdk/path-parameters/.mock/definition/user.yml b/seed/python-sdk/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/python-sdk/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/python-sdk/path-parameters/.mock/fern.config.json b/seed/python-sdk/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/python-sdk/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/python-sdk/path-parameters/.mock/generators.yml b/seed/python-sdk/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/python-sdk/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/python-sdk/path-parameters/README.md b/seed/python-sdk/path-parameters/README.md new file mode 100644 index 00000000000..e37e94b3a7d --- /dev/null +++ b/seed/python-sdk/path-parameters/README.md @@ -0,0 +1,138 @@ +# Seed Python Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FPython) +[![pypi](https://img.shields.io/pypi/v/fern_path-parameters)](https://pypi.python.org/pypi/fern_path-parameters) + +The Seed Python library provides convenient access to the Seed API from Python. + +## Installation + +```sh +pip install fern_path-parameters +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```python +from seed import SeedPathParameters + +client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", +) +client.user.get_organization( + organization_id="organizationId", +) +``` + +## Async Client + +The SDK also exports an `async` client so that you can make non-blocking calls to our API. + +```python +import asyncio + +from seed import AsyncSeedPathParameters + +client = AsyncSeedPathParameters( + base_url="https://yourhost.com/path/to/api", +) + + +async def main() -> None: + await client.user.get_organization( + organization_id="organizationId", + ) + + +asyncio.run(main()) +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```python +from seed.core.api_error import ApiError + +try: + client.user.get_organization(...) +except ApiError as e: + print(e.status_code) + print(e.body) +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` request option to configure this behavior. + +```python +client.user.get_organization(..., request_options={ + "max_retries": 1 + }) + ``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```python + + from seed import SeedPathParameters + +client = SeedPathParameters( + ..., + timeout=20.0, +) + + + # Override timeout for a specific method + client.user.get_organization(..., request_options={ + "timeout_in_seconds": 1 + }) + ``` + +### Custom Client + +You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies +and transports. +```python +import httpx +from seed import SeedPathParameters + +client = SeedPathParameters( + ..., + httpx_client=httpx.Client( + proxies="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/path-parameters/pyproject.toml b/seed/python-sdk/path-parameters/pyproject.toml new file mode 100644 index 00000000000..a43a379ee23 --- /dev/null +++ b/seed/python-sdk/path-parameters/pyproject.toml @@ -0,0 +1,61 @@ +[tool.poetry] +name = "fern_path-parameters" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed", from = "src"} +] + +[project.urls] +Repository = 'https://github.com/path-parameters/fern' + +[tool.poetry.dependencies] +python = "^3.8" +httpx = ">=0.21.2" +pydantic = ">= 1.9.2" +pydantic-core = "^2.18.2" +typing_extensions = ">= 4.0.0" + +[tool.poetry.dev-dependencies] +mypy = "1.0.1" +pytest = "^7.4.0" +pytest-asyncio = "^0.23.5" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" +ruff = "^0.5.6" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.ruff] +line-length = 120 + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/python-sdk/path-parameters/reference.md b/seed/python-sdk/path-parameters/reference.md new file mode 100644 index 00000000000..a5fac6cc468 --- /dev/null +++ b/seed/python-sdk/path-parameters/reference.md @@ -0,0 +1,179 @@ +# Reference +## User +

client.user.get_organization(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedPathParameters + +client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", +) +client.user.get_organization( + organization_id="organizationId", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**organization_id:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.user.get_user(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedPathParameters + +client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", +) +client.user.get_user( + user_id="userId", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**user_id:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.user.get_organization_user(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedPathParameters + +client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", +) +client.user.get_organization_user( + organization_id="organizationId", + user_id="userId", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**organization_id:** `str` + +
+
+ +
+
+ +**user_id:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/python-sdk/path-parameters/resolved-snippet-templates.md b/seed/python-sdk/path-parameters/resolved-snippet-templates.md new file mode 100644 index 00000000000..065b183b4dc --- /dev/null +++ b/seed/python-sdk/path-parameters/resolved-snippet-templates.md @@ -0,0 +1,34 @@ +```python + + +client = SeedPathParameters(base_url="https://yourhost.com/path/to/api", ) +client.user.get_organization( + organization_id="organizationId" +) + +``` + + +```python + + +client = SeedPathParameters(base_url="https://yourhost.com/path/to/api", ) +client.user.get_user( + user_id="userId" +) + +``` + + +```python + + +client = SeedPathParameters(base_url="https://yourhost.com/path/to/api", ) +client.user.get_organization_user( + organization_id="organizationId", + user_id="userId" +) + +``` + + diff --git a/seed/python-sdk/path-parameters/snippet-templates.json b/seed/python-sdk/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..fd31ec6854d --- /dev/null +++ b/seed/python-sdk/path-parameters/snippet-templates.json @@ -0,0 +1,304 @@ +[ + { + "sdk": { + "package": "fern_path-parameters", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganization" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedPathParameters" + ], + "isOptional": true, + "templateString": "client = SeedPathParameters(base_url=\"https://yourhost.com/path/to/api\", )", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.user.get_organization(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "organization_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "organizationId", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedPathParameters" + ], + "isOptional": true, + "templateString": "client = AsyncSeedPathParameters(base_url=\"https://yourhost.com/path/to/api\", )", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.user.get_organization(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "organization_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "organizationId", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_path-parameters", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/user/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedPathParameters" + ], + "isOptional": true, + "templateString": "client = SeedPathParameters(base_url=\"https://yourhost.com/path/to/api\", )", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.user.get_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "user_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "userId", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedPathParameters" + ], + "isOptional": true, + "templateString": "client = AsyncSeedPathParameters(base_url=\"https://yourhost.com/path/to/api\", )", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.user.get_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "user_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "userId", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_path-parameters", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganizationUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedPathParameters" + ], + "isOptional": true, + "templateString": "client = SeedPathParameters(base_url=\"https://yourhost.com/path/to/api\", )", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.user.get_organization_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "organization_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "organizationId", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "user_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "userId", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedPathParameters" + ], + "isOptional": true, + "templateString": "client = AsyncSeedPathParameters(base_url=\"https://yourhost.com/path/to/api\", )", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.user.get_organization_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "organization_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "organizationId", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "user_id=$FERN_INPUT", + "templateInputs": [ + { + "location": "PATH", + "path": "userId", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + } +] \ No newline at end of file diff --git a/seed/python-sdk/path-parameters/snippet.json b/seed/python-sdk/path-parameters/snippet.json new file mode 100644 index 00000000000..1efdeeca47b --- /dev/null +++ b/seed/python-sdk/path-parameters/snippet.json @@ -0,0 +1,44 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": "default", + "id": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganization" + }, + "snippet": { + "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.get_organization(\n organization_id=\"organizationId\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.get_organization(\n organization_id=\"organizationId\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/user/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getUser" + }, + "snippet": { + "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.get_user(\n user_id=\"userId\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.get_user(\n user_id=\"userId\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganizationUser" + }, + "snippet": { + "sync_client": "from seed import SeedPathParameters\n\nclient = SeedPathParameters(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.get_organization_user(\n organization_id=\"organizationId\",\n user_id=\"userId\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedPathParameters\n\nclient = AsyncSeedPathParameters(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.get_organization_user(\n organization_id=\"organizationId\",\n user_id=\"userId\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + } + ] +} \ No newline at end of file diff --git a/seed/python-sdk/path-parameters/src/seed/__init__.py b/seed/python-sdk/path-parameters/src/seed/__init__.py new file mode 100644 index 00000000000..5cee96ae9ac --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/__init__.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +from . import user +from .client import AsyncSeedPathParameters, SeedPathParameters +from .user import User +from .version import __version__ + +__all__ = ["AsyncSeedPathParameters", "SeedPathParameters", "User", "__version__", "user"] diff --git a/seed/python-sdk/path-parameters/src/seed/client.py b/seed/python-sdk/path-parameters/src/seed/client.py new file mode 100644 index 00000000000..bbe38efb20a --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/client.py @@ -0,0 +1,104 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import httpx +from .core.client_wrapper import SyncClientWrapper +from .user.client import UserClient +from .core.client_wrapper import AsyncClientWrapper +from .user.client import AsyncUserClient + + +class SeedPathParameters: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import SeedPathParameters + + client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None, + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = SyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.user = UserClient(client_wrapper=self._client_wrapper) + + +class AsyncSeedPathParameters: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import AsyncSeedPathParameters + + client = AsyncSeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None, + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = AsyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.AsyncClient(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.user = AsyncUserClient(client_wrapper=self._client_wrapper) diff --git a/seed/python-sdk/path-parameters/src/seed/core/__init__.py b/seed/python-sdk/path-parameters/src/seed/core/__init__.py new file mode 100644 index 00000000000..f03aecbfe18 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/__init__.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +from .api_error import ApiError +from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper +from .datetime_utils import serialize_datetime +from .file import File, convert_file_dict_to_httpx_tuples, with_content_type +from .http_client import AsyncHttpClient, HttpClient +from .jsonable_encoder import jsonable_encoder +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions +from .serialization import FieldMetadata, convert_and_respect_annotation_metadata + +__all__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "BaseClientWrapper", + "FieldMetadata", + "File", + "HttpClient", + "IS_PYDANTIC_V2", + "RequestOptions", + "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", + "with_content_type", +] diff --git a/seed/python-sdk/path-parameters/src/seed/core/api_error.py b/seed/python-sdk/path-parameters/src/seed/core/api_error.py new file mode 100644 index 00000000000..2e9fc5431cd --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/api_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + + +class ApiError(Exception): + status_code: typing.Optional[int] + body: typing.Any + + def __init__(self, *, status_code: typing.Optional[int] = None, body: typing.Any = None): + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/path-parameters/src/seed/core/client_wrapper.py b/seed/python-sdk/path-parameters/src/seed/core/client_wrapper.py new file mode 100644 index 00000000000..e7d732b0432 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/client_wrapper.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import httpx +from .http_client import HttpClient +from .http_client import AsyncHttpClient + + +class BaseClientWrapper: + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None): + self._base_url = base_url + self._timeout = timeout + + def get_headers(self) -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = { + "X-Fern-Language": "Python", + "X-Fern-SDK-Name": "fern_path-parameters", + "X-Fern-SDK-Version": "0.0.1", + } + return headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + +class SyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.Client): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.AsyncClient): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + ) diff --git a/seed/python-sdk/path-parameters/src/seed/core/datetime_utils.py b/seed/python-sdk/path-parameters/src/seed/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/path-parameters/src/seed/core/file.py b/seed/python-sdk/path-parameters/src/seed/core/file.py new file mode 100644 index 00000000000..44b0d27c089 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/file.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = Union[IO[bytes], bytes, str] +File = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[ + Optional[str], + FileContent, + Optional[str], + Mapping[str, str], + ], +] + + +def convert_file_dict_to_httpx_tuples( + d: Dict[str, Union[File, List[File]]], +) -> List[Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples + + +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ + if isinstance(file, tuple): + if len(file) == 2: + filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore + return (filename, content, default_content_type) + elif len(file) == 3: + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) + elif len(file) == 4: + filename, content, file_content_type, headers = cast( # type: ignore + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file + ) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) + else: + raise ValueError(f"Unexpected tuple length: {len(file)}") + return (None, file, default_content_type) diff --git a/seed/python-sdk/path-parameters/src/seed/core/http_client.py b/seed/python-sdk/path-parameters/src/seed/core/http_client.py new file mode 100644 index 00000000000..1a1a1311a66 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/http_client.py @@ -0,0 +1,499 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import json +import re +import time +import typing +import urllib.parse +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx + +from .file import File, convert_file_dict_to_httpx_tuples +from .jsonable_encoder import jsonable_encoder +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions + +INITIAL_RETRY_DELAY_SECONDS = 0.5 +MAX_RETRY_DELAY_SECONDS = 10 +MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30 + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER: + return retry_after + + # Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS. + retry_delay = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + + # Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries. + timeout = retry_delay * (1 - 0.25 * random()) + return timeout if timeout >= 0 else 0 + + +def _should_retry(response: httpx.Response) -> bool: + retriable_400s = [429, 408, 409] + return response.status_code >= 500 or response.status_code in retriable_400s + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], + omit: typing.Optional[typing.Any], +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + response = self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit) + else None + ), + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit) + else None + ), + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + # Add the input to each of these and do None-safety checks + response = await self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if files is not None + else None + ), + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + async with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if files is not None + else None + ), + timeout=timeout, + ) as stream: + yield stream diff --git a/seed/python-sdk/path-parameters/src/seed/core/jsonable_encoder.py b/seed/python-sdk/path-parameters/src/seed/core/jsonable_encoder.py new file mode 100644 index 00000000000..1b631e9017c --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/jsonable_encoder.py @@ -0,0 +1,101 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union + +import pydantic + +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + encode_by_type, + to_jsonable_with_fallback, +) + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) diff --git a/seed/python-sdk/path-parameters/src/seed/core/pydantic_utilities.py b/seed/python-sdk/path-parameters/src/seed/core/pydantic_utilities.py new file mode 100644 index 00000000000..ee8f0e4105f --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/pydantic_utilities.py @@ -0,0 +1,296 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict + +import typing_extensions + +import pydantic + +from .datetime_utils import serialize_datetime +from .serialization import convert_and_respect_annotation_metadata + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + get_origin as get_origin, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_union as is_union, + ) + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(dealiased_object) + else: + return pydantic.parse_obj_as(type_, dealiased_object) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + # Allow fields begining with `model_` to be used in the model + protected_namespaces=(), + ) # type: ignore # Pydantic v2 + + @pydantic.model_serializer(mode="wrap", when_used="json") # type: ignore # Pydantic v2 + def serialize_model(self, handler: pydantic.SerializerFunctionWrapHandler) -> typing.Any: # type: ignore # Pydantic v2 + serialized = handler(self) + data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} + return data + + else: + + class Config: + smart_union = True + json_encoders = {dt.datetime: serialize_datetime} + + @classmethod + def model_construct( + cls: typing.Type["Model"], _fields_set: typing.Optional[typing.Set[str]] = None, **values: typing.Any + ) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + return cls.construct(_fields_set, **dealiased_object) + + @classmethod + def construct( + cls: typing.Type["Model"], _fields_set: typing.Optional[typing.Set[str]] = None, **values: typing.Any + ) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + if IS_PYDANTIC_V2: + return super().model_construct(_fields_set, **dealiased_object) # type: ignore # Pydantic v2 + else: + return super().construct(_fields_set, **dealiased_object) + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multi-plexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + dict_dump = deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields: typing.Any = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + return convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write") + + +def _union_list_of_pydantic_dicts( + source: typing.List[typing.Any], destination: typing.List[typing.Any] +) -> typing.List[typing.Any]: + converted_list: typing.List[typing.Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] # type: ignore + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel: typing_extensions.TypeAlias = V2RootModel # type: ignore +else: + UniversalRootModel: typing_extensions.TypeAlias = UniversalBaseModel # type: ignore + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...]] = ( + defaultdict(tuple) + ) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator( + pre: bool = False, +) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return pydantic.model_validator(mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + return pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + return pydantic.validator(field_name, pre=pre)(func) # type: ignore # Pydantic v1 + + return decorator + + +PydanticField = typing.Union[ModelField, pydantic.fields.FieldInfo] + + +def _get_model_fields( + model: typing.Type["Model"], +) -> typing.Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return model.model_fields # type: ignore # Pydantic v2 + else: + return model.__fields__ # type: ignore # Pydantic v1 + + +def _get_field_default(field: PydanticField) -> typing.Any: + try: + value = field.get_default() # type: ignore # Pydantic < v1.10.15 + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/seed/python-sdk/path-parameters/src/seed/core/query_encoder.py b/seed/python-sdk/path-parameters/src/seed/core/query_encoder.py new file mode 100644 index 00000000000..3183001d404 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/query_encoder.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, List, Optional, Tuple + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: + result = [] + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.extend(traverse_query_dict(v, key)) + elif isinstance(v, list): + for arr_v in v: + if isinstance(arr_v, dict): + result.extend(traverse_query_dict(arr_v, key)) + else: + result.append((key, arr_v)) + else: + result.append((key, v)) + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + elif isinstance(query_value, list): + encoded_values: List[Tuple[str, Any]] = [] + for value in query_value: + if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): + if isinstance(value, pydantic.BaseModel): + obj_dict = value.dict(by_alias=True) + elif isinstance(value, dict): + obj_dict = value + + encoded_values.extend(single_query_encoder(query_key, obj_dict)) + else: + encoded_values.append((query_key, value)) + + return encoded_values + + return [(query_key, query_value)] + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: + if query is None: + return None + + encoded_query = [] + for k, v in query.items(): + encoded_query.extend(single_query_encoder(k, v)) + return encoded_query diff --git a/seed/python-sdk/path-parameters/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/path-parameters/src/seed/core/remove_none_from_dict.py new file mode 100644 index 00000000000..c2298143f14 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/seed/python-sdk/path-parameters/src/seed/core/request_options.py b/seed/python-sdk/path-parameters/src/seed/core/request_options.py new file mode 100644 index 00000000000..1b38804432b --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/request_options.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout_in_seconds: int. The number of seconds to await an API call before timing out. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + + - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. + """ + + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] + chunk_size: NotRequired[int] diff --git a/seed/python-sdk/path-parameters/src/seed/core/serialization.py b/seed/python-sdk/path-parameters/src/seed/core/serialization.py new file mode 100644 index 00000000000..cb5dcbf93a9 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/core/serialization.py @@ -0,0 +1,272 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import typing_extensions + +import pydantic + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/seed/python-sdk/path-parameters/src/seed/py.typed b/seed/python-sdk/path-parameters/src/seed/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/python-sdk/path-parameters/src/seed/user/__init__.py b/seed/python-sdk/path-parameters/src/seed/user/__init__.py new file mode 100644 index 00000000000..4d928acf301 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/user/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import User + +__all__ = ["User"] diff --git a/seed/python-sdk/path-parameters/src/seed/user/client.py b/seed/python-sdk/path-parameters/src/seed/user/client.py new file mode 100644 index 00000000000..0c7b0eaafd9 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/user/client.py @@ -0,0 +1,317 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import SyncClientWrapper +import typing +from ..core.request_options import RequestOptions +from .types.user import User +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from json.decoder import JSONDecodeError +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper + + +class UserClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_organization( + self, organization_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> User: + """ + Parameters + ---------- + organization_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + from seed import SeedPathParameters + + client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + client.user.get_organization( + organization_id="organizationId", + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"user/organizations/{jsonable_encoder(organization_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + User, + parse_obj_as( + type_=User, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_user(self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> User: + """ + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + from seed import SeedPathParameters + + client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + client.user.get_user( + user_id="userId", + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"user/users/{jsonable_encoder(user_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + User, + parse_obj_as( + type_=User, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_organization_user( + self, organization_id: str, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> User: + """ + Parameters + ---------- + organization_id : str + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + from seed import SeedPathParameters + + client = SeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + client.user.get_organization_user( + organization_id="organizationId", + user_id="userId", + ) + """ + _response = self._client_wrapper.httpx_client.request( + f"user/organizations/{jsonable_encoder(organization_id)}/users/{jsonable_encoder(user_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + User, + parse_obj_as( + type_=User, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncUserClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_organization( + self, organization_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> User: + """ + Parameters + ---------- + organization_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + import asyncio + + from seed import AsyncSeedPathParameters + + client = AsyncSeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.user.get_organization( + organization_id="organizationId", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"user/organizations/{jsonable_encoder(organization_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + User, + parse_obj_as( + type_=User, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_user(self, user_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> User: + """ + Parameters + ---------- + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + import asyncio + + from seed import AsyncSeedPathParameters + + client = AsyncSeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.user.get_user( + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"user/users/{jsonable_encoder(user_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + User, + parse_obj_as( + type_=User, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_organization_user( + self, organization_id: str, user_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> User: + """ + Parameters + ---------- + organization_id : str + + user_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + import asyncio + + from seed import AsyncSeedPathParameters + + client = AsyncSeedPathParameters( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.user.get_organization_user( + organization_id="organizationId", + user_id="userId", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + f"user/organizations/{jsonable_encoder(organization_id)}/users/{jsonable_encoder(user_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast( + User, + parse_obj_as( + type_=User, # type: ignore + object_=_response.json(), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/seed/python-sdk/path-parameters/src/seed/user/types/__init__.py b/seed/python-sdk/path-parameters/src/seed/user/types/__init__.py new file mode 100644 index 00000000000..b22b663beed --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/user/types/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .user import User + +__all__ = ["User"] diff --git a/seed/python-sdk/path-parameters/src/seed/user/types/user.py b/seed/python-sdk/path-parameters/src/seed/user/types/user.py new file mode 100644 index 00000000000..3de5d9c1b9d --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/user/types/user.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +from ...core.pydantic_utilities import UniversalBaseModel +import typing +from ...core.pydantic_utilities import IS_PYDANTIC_V2 +import pydantic + + +class User(UniversalBaseModel): + name: str + tags: typing.List[str] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/path-parameters/src/seed/version.py b/seed/python-sdk/path-parameters/src/seed/version.py new file mode 100644 index 00000000000..e59e6530060 --- /dev/null +++ b/seed/python-sdk/path-parameters/src/seed/version.py @@ -0,0 +1,3 @@ +from importlib import metadata + +__version__ = metadata.version("fern_path-parameters") diff --git a/seed/python-sdk/path-parameters/tests/__init__.py b/seed/python-sdk/path-parameters/tests/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/path-parameters/tests/conftest.py b/seed/python-sdk/path-parameters/tests/conftest.py new file mode 100644 index 00000000000..f745ffd4268 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/conftest.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed import SeedPathParameters +import os +import pytest +from seed import AsyncSeedPathParameters + + +@pytest.fixture +def client() -> SeedPathParameters: + return SeedPathParameters(base_url=os.getenv("TESTS_BASE_URL", "base_url")) + + +@pytest.fixture +def async_client() -> AsyncSeedPathParameters: + return AsyncSeedPathParameters(base_url=os.getenv("TESTS_BASE_URL", "base_url")) diff --git a/seed/python-sdk/path-parameters/tests/custom/test_client.py b/seed/python-sdk/path-parameters/tests/custom/test_client.py new file mode 100644 index 00000000000..73f811f5ede --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/custom/test_client.py @@ -0,0 +1,7 @@ +import pytest + + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True == True diff --git a/seed/python-sdk/path-parameters/tests/test_user.py b/seed/python-sdk/path-parameters/tests/test_user.py new file mode 100644 index 00000000000..fb1b1f4d16f --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/test_user.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed import SeedPathParameters +from seed import AsyncSeedPathParameters +import typing +from .utilities import validate_response + + +async def test_get_organization(client: SeedPathParameters, async_client: AsyncSeedPathParameters) -> None: + expected_response: typing.Any = {"name": "name", "tags": ["tags", "tags"]} + expected_types: typing.Any = {"name": None, "tags": ("list", {0: None, 1: None})} + response = client.user.get_organization(organization_id="organizationId") + validate_response(response, expected_response, expected_types) + + async_response = await async_client.user.get_organization(organization_id="organizationId") + validate_response(async_response, expected_response, expected_types) + + +async def test_get_user(client: SeedPathParameters, async_client: AsyncSeedPathParameters) -> None: + expected_response: typing.Any = {"name": "name", "tags": ["tags", "tags"]} + expected_types: typing.Any = {"name": None, "tags": ("list", {0: None, 1: None})} + response = client.user.get_user(user_id="userId") + validate_response(response, expected_response, expected_types) + + async_response = await async_client.user.get_user(user_id="userId") + validate_response(async_response, expected_response, expected_types) + + +async def test_get_organization_user(client: SeedPathParameters, async_client: AsyncSeedPathParameters) -> None: + expected_response: typing.Any = {"name": "name", "tags": ["tags", "tags"]} + expected_types: typing.Any = {"name": None, "tags": ("list", {0: None, 1: None})} + response = client.user.get_organization_user(organization_id="organizationId", user_id="userId") + validate_response(response, expected_response, expected_types) + + async_response = await async_client.user.get_organization_user(organization_id="organizationId", user_id="userId") + validate_response(async_response, expected_response, expected_types) diff --git a/seed/python-sdk/path-parameters/tests/utilities.py b/seed/python-sdk/path-parameters/tests/utilities.py new file mode 100644 index 00000000000..3d228806a9c --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utilities.py @@ -0,0 +1,162 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import uuid + +from dateutil import parser + +import pydantic + + +def cast_field(json_expectation: typing.Any, type_expectation: typing.Any) -> typing.Any: + # Cast these specific types which come through as string and expect our + # models to cast to the correct type. + if type_expectation == "uuid": + return uuid.UUID(json_expectation) + elif type_expectation == "date": + return parser.parse(json_expectation).date() + elif type_expectation == "datetime": + return parser.parse(json_expectation) + elif type_expectation == "set": + return set(json_expectation) + elif type_expectation == "integer": + # Necessary as we allow numeric keys, but JSON makes them strings + return int(json_expectation) + + return json_expectation + + +def validate_field(response: typing.Any, json_expectation: typing.Any, type_expectation: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectation == "no_validate": + return + + is_container_of_complex_type = False + # Parse types in containers, note that dicts are handled within `validate_response` + if isinstance(json_expectation, list): + if isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + + cast_json_expectation = [] + for idx, ex in enumerate(json_expectation): + if isinstance(contents_expectation, dict): + entry_expectation = contents_expectation.get(idx) + if isinstance(entry_expectation, dict): + is_container_of_complex_type = True + validate_response( + response=response[idx], + json_expectation=ex, + type_expectations=entry_expectation, + ) + else: + cast_json_expectation.append(cast_field(ex, entry_expectation)) + else: + cast_json_expectation.append(ex) + json_expectation = cast_json_expectation + + # Note that we explicitly do not allow for sets of pydantic models as they are not hashable, so + # if any of the values of the set have a type_expectation of a dict, we're assuming it's a pydantic + # model and keeping it a list. + if container_expectation != "set" or not any( + map( + lambda value: isinstance(value, dict), + list(contents_expectation.values()), + ) + ): + json_expectation = cast_field(json_expectation, container_expectation) + elif isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + if isinstance(contents_expectation, dict): + json_expectation = { + cast_field( + key, + contents_expectation.get(idx)[0] # type: ignore + if contents_expectation.get(idx) is not None + else None, + ): cast_field( + value, + contents_expectation.get(idx)[1] # type: ignore + if contents_expectation.get(idx) is not None + else None, + ) + for idx, (key, value) in enumerate(json_expectation.items()) + } + else: + json_expectation = cast_field(json_expectation, container_expectation) + elif type_expectation is not None: + json_expectation = cast_field(json_expectation, type_expectation) + + # When dealing with containers of models, etc. we're validating them implicitly, so no need to check the resultant list + if not is_container_of_complex_type: + assert ( + json_expectation == response + ), "Primitives found, expected: {0} (type: {1}), Actual: {2} (type: {3})".format( + json_expectation, type(json_expectation), response, type(response) + ) + + +# Arg type_expectations is a deeply nested structure that matches the response, but with the values replaced with the expected types +def validate_response(response: typing.Any, json_expectation: typing.Any, type_expectations: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectations == "no_validate": + return + + if ( + not isinstance(response, list) + and not isinstance(response, dict) + and not issubclass(type(response), pydantic.BaseModel) + ): + validate_field( + response=response, + json_expectation=json_expectation, + type_expectation=type_expectations, + ) + return + + if isinstance(response, list): + assert len(response) == len(json_expectation), "Length mismatch, expected: {0}, Actual: {1}".format( + len(response), len(json_expectation) + ) + content_expectation = type_expectations + if isinstance(type_expectations, tuple): + content_expectation = type_expectations[1] + for idx, item in enumerate(response): + validate_response( + response=item, + json_expectation=json_expectation[idx], + type_expectations=content_expectation[idx], + ) + else: + response_json = response + if issubclass(type(response), pydantic.BaseModel): + response_json = response.dict(by_alias=True) + + for key, value in json_expectation.items(): + assert key in response_json, "Field {0} not found within the response object: {1}".format( + key, response_json + ) + + type_expectation = None + if type_expectations is not None and isinstance(type_expectations, dict): + type_expectation = type_expectations.get(key) + + # If your type_expectation is a tuple then you have a container field, process it as such + # Otherwise, we're just validating a single field that's a pydantic model. + if isinstance(value, dict) and not isinstance(type_expectation, tuple): + validate_response( + response=response_json[key], + json_expectation=value, + type_expectations=type_expectation, + ) + else: + validate_field( + response=response_json[key], + json_expectation=value, + type_expectation=type_expectation, + ) + + # Ensure there are no additional fields here either + del response_json[key] + assert len(response_json) == 0, "Additional fields found, expected None: {0}".format(response_json) diff --git a/seed/python-sdk/path-parameters/tests/utils/__init__.py b/seed/python-sdk/path-parameters/tests/utils/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/__init__.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/__init__.py new file mode 100644 index 00000000000..3a1c852e71e --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from .circle import CircleParams +from .object_with_defaults import ObjectWithDefaultsParams +from .object_with_optional_field import ObjectWithOptionalFieldParams +from .shape import ShapeParams, Shape_CircleParams, Shape_SquareParams +from .square import SquareParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +__all__ = [ + "CircleParams", + "ObjectWithDefaultsParams", + "ObjectWithOptionalFieldParams", + "ShapeParams", + "Shape_CircleParams", + "Shape_SquareParams", + "SquareParams", + "UndiscriminatedShapeParams", +] diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/circle.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/circle.py new file mode 100644 index 00000000000..6522dc58c5a --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/circle.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class CircleParams(typing_extensions.TypedDict): + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/color.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/color.py new file mode 100644 index 00000000000..2aa2c4c52f0 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/color.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_defaults.py new file mode 100644 index 00000000000..ef14f7b2c9d --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_defaults.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +import typing_extensions + + +class ObjectWithDefaultsParams(typing_extensions.TypedDict): + """ + Defines properties with default values and validation rules. + """ + + decimal: typing_extensions.NotRequired[float] + string: typing_extensions.NotRequired[str] + required_string: str diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_optional_field.py new file mode 100644 index 00000000000..fc5a379b967 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/object_with_optional_field.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +import typing +import typing_extensions +from seed.core.serialization import FieldMetadata +import datetime as dt +import uuid +from .color import Color +from .shape import ShapeParams +from .undiscriminated_shape import UndiscriminatedShapeParams + + +class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): + literal: typing.Literal["lit_one"] + string: typing_extensions.NotRequired[str] + integer: typing_extensions.NotRequired[int] + long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] + double: typing_extensions.NotRequired[float] + bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] + datetime: typing_extensions.NotRequired[dt.datetime] + date: typing_extensions.NotRequired[dt.date] + uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] + base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] + list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] + set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] + map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] + enum: typing_extensions.NotRequired[Color] + union: typing_extensions.NotRequired[ShapeParams] + second_union: typing_extensions.NotRequired[ShapeParams] + undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] + any: typing.Optional[typing.Any] diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/shape.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/shape.py new file mode 100644 index 00000000000..ae113ae0609 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/shape.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations +import typing_extensions +import typing_extensions +import typing +from seed.core.serialization import FieldMetadata + + +class Base(typing_extensions.TypedDict): + id: str + + +class Shape_CircleParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] + + +class Shape_SquareParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] + + +ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/square.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/square.py new file mode 100644 index 00000000000..7f6f79a3ddc --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/square.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class SquareParams(typing_extensions.TypedDict): + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/path-parameters/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/path-parameters/tests/utils/assets/models/undiscriminated_shape.py new file mode 100644 index 00000000000..68876a23c38 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/assets/models/undiscriminated_shape.py @@ -0,0 +1,9 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing +from .circle import CircleParams +from .square import SquareParams + +UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/path-parameters/tests/utils/test_http_client.py b/seed/python-sdk/path-parameters/tests/utils/test_http_client.py new file mode 100644 index 00000000000..a541bae6531 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/test_http_client.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.http_client import get_request_body +from seed.core.request_options import RequestOptions + + +def get_request_options() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later"}} + + +def test_get_json_request_body() -> None: + json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) + assert json_body == {"hello": "world"} + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"goodbye": "world", "see you": "later"} + assert data_body_extras is None + + +def test_get_files_request_body() -> None: + json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) + assert data_body == {"hello": "world"} + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None + ) + + assert data_body_extras == {"goodbye": "world", "see you": "later"} + assert json_body_extras is None + + +def test_get_none_request_body() -> None: + json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) + assert data_body is None + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"see you": "later"} + assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/path-parameters/tests/utils/test_query_encoding.py b/seed/python-sdk/path-parameters/tests/utils/test_query_encoding.py new file mode 100644 index 00000000000..e075394a502 --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/test_query_encoding.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + + +from seed.core.query_encoder import encode_query + + +def test_query_encoding_deep_objects() -> None: + assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")] + assert encode_query({"hello_world": {"hello": "world"}}) == [("hello_world[hello]", "world")] + assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == [ + ("hello_world[hello][world]", "today"), + ("hello_world[test]", "this"), + ("hi", "there"), + ] + + +def test_query_encoding_deep_object_arrays() -> None: + assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [ + ("objects[key]", "hello"), + ("objects[value]", "world"), + ("objects[key]", "foo"), + ("objects[value]", "bar"), + ] + assert encode_query( + {"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]} + ) == [ + ("users[name]", "string"), + ("users[tags]", "string"), + ("users[name]", "string2"), + ("users[tags]", "string2"), + ("users[tags]", "string3"), + ] + + +def test_encode_query_with_none() -> None: + encoded = encode_query(None) + assert encoded == None diff --git a/seed/python-sdk/path-parameters/tests/utils/test_serialization.py b/seed/python-sdk/path-parameters/tests/utils/test_serialization.py new file mode 100644 index 00000000000..dd74836366c --- /dev/null +++ b/seed/python-sdk/path-parameters/tests/utils/test_serialization.py @@ -0,0 +1,72 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import List, Any + +from seed.core.serialization import convert_and_respect_annotation_metadata +from .assets.models import ShapeParams, ObjectWithOptionalFieldParams + + +UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} +UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} + + +def test_convert_and_respect_annotation_metadata() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "bool_": True, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} + + +def test_convert_and_respect_annotation_metadata_in_list() -> None: + data: List[ObjectWithOptionalFieldParams] = [ + {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, + ] + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write" + ) + + assert converted == [ + {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, + ] + + +def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "union": UNION_TEST, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + + assert converted == { + "string": "string", + "long": 12345, + "union": UNION_TEST_CONVERTED, + "literal": "lit_one", + "any": "any", + } + + +def test_convert_and_respect_annotation_metadata_in_union() -> None: + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write") + + assert converted == UNION_TEST_CONVERTED + + +def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: + data: Any = {} + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write") + assert converted == data diff --git a/seed/ruby-model/path-parameters/.mock/definition/api.yml b/seed/ruby-model/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/ruby-model/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/ruby-model/path-parameters/.mock/definition/user.yml b/seed/ruby-model/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/ruby-model/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/ruby-model/path-parameters/.mock/fern.config.json b/seed/ruby-model/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ruby-model/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ruby-model/path-parameters/.mock/generators.yml b/seed/ruby-model/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/ruby-model/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/ruby-model/path-parameters/.rubocop.yml b/seed/ruby-model/path-parameters/.rubocop.yml new file mode 100644 index 00000000000..c1d2344d6e6 --- /dev/null +++ b/seed/ruby-model/path-parameters/.rubocop.yml @@ -0,0 +1,36 @@ +AllCops: + TargetRubyVersion: 2.7 + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + Enabled: true + EnforcedStyle: double_quotes + +Layout/FirstHashElementLineBreak: + Enabled: true + +Layout/MultilineHashKeyLineBreaks: + Enabled: true + +# Generated files may be more complex than standard, disable +# these rules for now as a known limitation. +Metrics/ParameterLists: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false diff --git a/seed/ruby-model/path-parameters/Gemfile b/seed/ruby-model/path-parameters/Gemfile new file mode 100644 index 00000000000..49bd9cd0173 --- /dev/null +++ b/seed/ruby-model/path-parameters/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +gem "minitest", "~> 5.0" +gem "rake", "~> 13.0" +gem "rubocop", "~> 1.21" diff --git a/seed/ruby-model/path-parameters/Rakefile b/seed/ruby-model/path-parameters/Rakefile new file mode 100644 index 00000000000..2bdbce0cf2c --- /dev/null +++ b/seed/ruby-model/path-parameters/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "rake/testtask" +require "rubocop/rake_task" + +task default: %i[test rubocop] + +Rake::TestTask.new do |t| + t.pattern = "./test/**/test_*.rb" +end + +RuboCop::RakeTask.new diff --git a/seed/ruby-model/path-parameters/lib/gemconfig.rb b/seed/ruby-model/path-parameters/lib/gemconfig.rb new file mode 100644 index 00000000000..8e2937b0c42 --- /dev/null +++ b/seed/ruby-model/path-parameters/lib/gemconfig.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module SeedPathParametersClient + module Gemconfig + VERSION = "" + AUTHORS = [""].freeze + EMAIL = "" + SUMMARY = "" + DESCRIPTION = "" + HOMEPAGE = "https://github.com/REPO/URL" + SOURCE_CODE_URI = "https://github.com/REPO/URL" + CHANGELOG_URI = "https://github.com/REPO/URL/blob/master/CHANGELOG.md" + end +end diff --git a/seed/ruby-model/path-parameters/lib/seed_path_parameters_client.rb b/seed/ruby-model/path-parameters/lib/seed_path_parameters_client.rb new file mode 100644 index 00000000000..5985a227312 --- /dev/null +++ b/seed/ruby-model/path-parameters/lib/seed_path_parameters_client.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative "seed_path_parameters_client/user/types/user" diff --git a/seed/ruby-model/path-parameters/lib/seed_path_parameters_client/user/types/user.rb b/seed/ruby-model/path-parameters/lib/seed_path_parameters_client/user/types/user.rb new file mode 100644 index 00000000000..eb1597e8b5b --- /dev/null +++ b/seed/ruby-model/path-parameters/lib/seed_path_parameters_client/user/types/user.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "ostruct" +require "json" + +module SeedPathParametersClient + class User + class User + # @return [String] + attr_reader :name + # @return [Array] + attr_reader :tags + # @return [OpenStruct] Additional properties unmapped to the current class definition + attr_reader :additional_properties + # @return [Object] + attr_reader :_field_set + protected :_field_set + + OMIT = Object.new + + # @param name [String] + # @param tags [Array] + # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition + # @return [SeedPathParametersClient::User::User] + def initialize(name:, tags:, additional_properties: nil) + @name = name + @tags = tags + @additional_properties = additional_properties + @_field_set = { "name": name, "tags": tags } + end + + # Deserialize a JSON object to an instance of User + # + # @param json_object [String] + # @return [SeedPathParametersClient::User::User] + def self.from_json(json_object:) + struct = JSON.parse(json_object, object_class: OpenStruct) + parsed_json = JSON.parse(json_object) + name = parsed_json["name"] + tags = parsed_json["tags"] + new( + name: name, + tags: tags, + additional_properties: struct + ) + end + + # Serialize an instance of User to a JSON object + # + # @return [String] + def to_json(*_args) + @_field_set&.to_json + end + + # Leveraged for Union-type generation, validate_raw attempts to parse the given + # hash and check each fields type against the current object's property + # definitions. + # + # @param obj [Object] + # @return [Void] + def self.validate_raw(obj:) + obj.name.is_a?(String) != false || raise("Passed value for field obj.name is not the expected type, validation failed.") + obj.tags.is_a?(Array) != false || raise("Passed value for field obj.tags is not the expected type, validation failed.") + end + end + end +end diff --git a/seed/ruby-model/path-parameters/seed_path_parameters_client.gemspec b/seed/ruby-model/path-parameters/seed_path_parameters_client.gemspec new file mode 100644 index 00000000000..bf71165f205 --- /dev/null +++ b/seed/ruby-model/path-parameters/seed_path_parameters_client.gemspec @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "lib/gemconfig" + +Gem::Specification.new do |spec| + spec.name = "seed_path_parameters_client" + spec.version = SeedPathParametersClient::Gemconfig::VERSION + spec.authors = SeedPathParametersClient::Gemconfig::AUTHORS + spec.email = SeedPathParametersClient::Gemconfig::EMAIL + spec.summary = SeedPathParametersClient::Gemconfig::SUMMARY + spec.description = SeedPathParametersClient::Gemconfig::DESCRIPTION + spec.homepage = SeedPathParametersClient::Gemconfig::HOMEPAGE + spec.required_ruby_version = ">= 2.7.0" + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = SeedPathParametersClient::Gemconfig::SOURCE_CODE_URI + spec.metadata["changelog_uri"] = SeedPathParametersClient::Gemconfig::CHANGELOG_URI + spec.files = Dir.glob("lib/**/*") + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] +end diff --git a/seed/ruby-model/path-parameters/snippet-templates.json b/seed/ruby-model/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-model/path-parameters/snippet.json b/seed/ruby-model/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-model/path-parameters/test/test_helper.rb b/seed/ruby-model/path-parameters/test/test_helper.rb new file mode 100644 index 00000000000..fab886906b0 --- /dev/null +++ b/seed/ruby-model/path-parameters/test/test_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) + +require "minitest/autorun" +require "seed_path_parameters_client" diff --git a/seed/ruby-model/path-parameters/test/test_seed_path_parameters_client.rb b/seed/ruby-model/path-parameters/test/test_seed_path_parameters_client.rb new file mode 100644 index 00000000000..d7dda39ea62 --- /dev/null +++ b/seed/ruby-model/path-parameters/test/test_seed_path_parameters_client.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative "test_helper" +require "seed_path_parameters_client" + +# Basic SeedPathParametersClient tests +class TestSeedPathParametersClient < Minitest::Test + def test_function + # SeedPathParametersClient::Client.new + end +end diff --git a/seed/ruby-sdk/path-parameters/.github/workflows/publish.yml b/seed/ruby-sdk/path-parameters/.github/workflows/publish.yml new file mode 100644 index 00000000000..239f4201abc --- /dev/null +++ b/seed/ruby-sdk/path-parameters/.github/workflows/publish.yml @@ -0,0 +1,26 @@ +name: Publish + +on: [push] +jobs: + publish: + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + + - name: Test gem + run: bundle install && bundle exec rake test + + - name: Build and Push Gem + env: + GEM_HOST_API_KEY: ${{ secrets. }} + run: | + gem build fern_path_parameters.gemspec + + gem push fern_path_parameters-*.gem --host diff --git a/seed/ruby-sdk/path-parameters/.gitignore b/seed/ruby-sdk/path-parameters/.gitignore new file mode 100644 index 00000000000..a97c182a2e1 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/.gitignore @@ -0,0 +1,10 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +*.gem +.env diff --git a/seed/ruby-sdk/path-parameters/.mock/definition/api.yml b/seed/ruby-sdk/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/ruby-sdk/path-parameters/.mock/definition/user.yml b/seed/ruby-sdk/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/ruby-sdk/path-parameters/.mock/fern.config.json b/seed/ruby-sdk/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ruby-sdk/path-parameters/.mock/generators.yml b/seed/ruby-sdk/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/ruby-sdk/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/ruby-sdk/path-parameters/.rubocop.yml b/seed/ruby-sdk/path-parameters/.rubocop.yml new file mode 100644 index 00000000000..c1d2344d6e6 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/.rubocop.yml @@ -0,0 +1,36 @@ +AllCops: + TargetRubyVersion: 2.7 + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + Enabled: true + EnforcedStyle: double_quotes + +Layout/FirstHashElementLineBreak: + Enabled: true + +Layout/MultilineHashKeyLineBreaks: + Enabled: true + +# Generated files may be more complex than standard, disable +# these rules for now as a known limitation. +Metrics/ParameterLists: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false diff --git a/seed/ruby-sdk/path-parameters/Gemfile b/seed/ruby-sdk/path-parameters/Gemfile new file mode 100644 index 00000000000..49bd9cd0173 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +gem "minitest", "~> 5.0" +gem "rake", "~> 13.0" +gem "rubocop", "~> 1.21" diff --git a/seed/ruby-sdk/path-parameters/README.md b/seed/ruby-sdk/path-parameters/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-sdk/path-parameters/Rakefile b/seed/ruby-sdk/path-parameters/Rakefile new file mode 100644 index 00000000000..2bdbce0cf2c --- /dev/null +++ b/seed/ruby-sdk/path-parameters/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "rake/testtask" +require "rubocop/rake_task" + +task default: %i[test rubocop] + +Rake::TestTask.new do |t| + t.pattern = "./test/**/test_*.rb" +end + +RuboCop::RakeTask.new diff --git a/seed/ruby-sdk/path-parameters/fern_path_parameters.gemspec b/seed/ruby-sdk/path-parameters/fern_path_parameters.gemspec new file mode 100644 index 00000000000..c157e57eae3 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/fern_path_parameters.gemspec @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative "lib/gemconfig" + +Gem::Specification.new do |spec| + spec.name = "fern_path_parameters" + spec.version = "0.0.1" + spec.authors = SeedPathParametersClient::Gemconfig::AUTHORS + spec.email = SeedPathParametersClient::Gemconfig::EMAIL + spec.summary = SeedPathParametersClient::Gemconfig::SUMMARY + spec.description = SeedPathParametersClient::Gemconfig::DESCRIPTION + spec.homepage = SeedPathParametersClient::Gemconfig::HOMEPAGE + spec.required_ruby_version = ">= 2.7.0" + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = SeedPathParametersClient::Gemconfig::SOURCE_CODE_URI + spec.metadata["changelog_uri"] = SeedPathParametersClient::Gemconfig::CHANGELOG_URI + spec.files = Dir.glob("lib/**/*") + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + spec.add_dependency "async-http-faraday", ">= 0.0", "< 1.0" + spec.add_dependency "faraday", ">= 1.10", "< 3.0" + spec.add_dependency "faraday-net_http", ">= 1.0", "< 4.0" + spec.add_dependency "faraday-retry", ">= 1.0", "< 3.0" +end diff --git a/seed/ruby-sdk/path-parameters/lib/fern_path_parameters.rb b/seed/ruby-sdk/path-parameters/lib/fern_path_parameters.rb new file mode 100644 index 00000000000..5550e7aff07 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/lib/fern_path_parameters.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative "types_export" +require_relative "requests" +require_relative "fern_path_parameters/user/client" + +module SeedPathParametersClient + class Client + # @return [SeedPathParametersClient::UserClient] + attr_reader :user + + # @param base_url [String] + # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. + # @param timeout_in_seconds [Long] + # @return [SeedPathParametersClient::Client] + def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) + @request_client = SeedPathParametersClient::RequestClient.new( + base_url: base_url, + max_retries: max_retries, + timeout_in_seconds: timeout_in_seconds + ) + @user = SeedPathParametersClient::UserClient.new(request_client: @request_client) + end + end + + class AsyncClient + # @return [SeedPathParametersClient::AsyncUserClient] + attr_reader :user + + # @param base_url [String] + # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. + # @param timeout_in_seconds [Long] + # @return [SeedPathParametersClient::AsyncClient] + def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) + @async_request_client = SeedPathParametersClient::AsyncRequestClient.new( + base_url: base_url, + max_retries: max_retries, + timeout_in_seconds: timeout_in_seconds + ) + @user = SeedPathParametersClient::AsyncUserClient.new(request_client: @async_request_client) + end + end +end diff --git a/seed/ruby-sdk/path-parameters/lib/fern_path_parameters/user/client.rb b/seed/ruby-sdk/path-parameters/lib/fern_path_parameters/user/client.rb new file mode 100644 index 00000000000..d973c848542 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/lib/fern_path_parameters/user/client.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +require_relative "../../requests" +require_relative "types/user" +require "async" + +module SeedPathParametersClient + class UserClient + # @return [SeedPathParametersClient::RequestClient] + attr_reader :request_client + + # @param request_client [SeedPathParametersClient::RequestClient] + # @return [SeedPathParametersClient::UserClient] + def initialize(request_client:) + @request_client = request_client + end + + # @param organization_id [String] + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [SeedPathParametersClient::User::User] + # @example + # path_parameters = SeedPathParametersClient::Client.new(base_url: "https://api.example.com") + # path_parameters.user.get_organization(organization_id: "organizationId") + def get_organization(organization_id:, request_options: nil) + response = @request_client.conn.get do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + unless request_options.nil? || request_options&.additional_body_parameters.nil? + req.body = { **(request_options&.additional_body_parameters || {}) }.compact + end + req.url "#{@request_client.get_url(request_options: request_options)}/user/organizations/#{organization_id}" + end + SeedPathParametersClient::User::User.from_json(json_object: response.body) + end + + # @param user_id [String] + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [SeedPathParametersClient::User::User] + # @example + # path_parameters = SeedPathParametersClient::Client.new(base_url: "https://api.example.com") + # path_parameters.user.get_user(user_id: "userId") + def get_user(user_id:, request_options: nil) + response = @request_client.conn.get do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + unless request_options.nil? || request_options&.additional_body_parameters.nil? + req.body = { **(request_options&.additional_body_parameters || {}) }.compact + end + req.url "#{@request_client.get_url(request_options: request_options)}/user/users/#{user_id}" + end + SeedPathParametersClient::User::User.from_json(json_object: response.body) + end + + # @param organization_id [String] + # @param user_id [String] + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [SeedPathParametersClient::User::User] + # @example + # path_parameters = SeedPathParametersClient::Client.new(base_url: "https://api.example.com") + # path_parameters.user.get_organization_user(organization_id: "organizationId", user_id: "userId") + def get_organization_user(organization_id:, user_id:, request_options: nil) + response = @request_client.conn.get do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + unless request_options.nil? || request_options&.additional_body_parameters.nil? + req.body = { **(request_options&.additional_body_parameters || {}) }.compact + end + req.url "#{@request_client.get_url(request_options: request_options)}/user/organizations/#{organization_id}/users/#{user_id}" + end + SeedPathParametersClient::User::User.from_json(json_object: response.body) + end + end + + class AsyncUserClient + # @return [SeedPathParametersClient::AsyncRequestClient] + attr_reader :request_client + + # @param request_client [SeedPathParametersClient::AsyncRequestClient] + # @return [SeedPathParametersClient::AsyncUserClient] + def initialize(request_client:) + @request_client = request_client + end + + # @param organization_id [String] + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [SeedPathParametersClient::User::User] + # @example + # path_parameters = SeedPathParametersClient::Client.new(base_url: "https://api.example.com") + # path_parameters.user.get_organization(organization_id: "organizationId") + def get_organization(organization_id:, request_options: nil) + Async do + response = @request_client.conn.get do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + unless request_options.nil? || request_options&.additional_body_parameters.nil? + req.body = { **(request_options&.additional_body_parameters || {}) }.compact + end + req.url "#{@request_client.get_url(request_options: request_options)}/user/organizations/#{organization_id}" + end + SeedPathParametersClient::User::User.from_json(json_object: response.body) + end + end + + # @param user_id [String] + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [SeedPathParametersClient::User::User] + # @example + # path_parameters = SeedPathParametersClient::Client.new(base_url: "https://api.example.com") + # path_parameters.user.get_user(user_id: "userId") + def get_user(user_id:, request_options: nil) + Async do + response = @request_client.conn.get do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + unless request_options.nil? || request_options&.additional_body_parameters.nil? + req.body = { **(request_options&.additional_body_parameters || {}) }.compact + end + req.url "#{@request_client.get_url(request_options: request_options)}/user/users/#{user_id}" + end + SeedPathParametersClient::User::User.from_json(json_object: response.body) + end + end + + # @param organization_id [String] + # @param user_id [String] + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [SeedPathParametersClient::User::User] + # @example + # path_parameters = SeedPathParametersClient::Client.new(base_url: "https://api.example.com") + # path_parameters.user.get_organization_user(organization_id: "organizationId", user_id: "userId") + def get_organization_user(organization_id:, user_id:, request_options: nil) + Async do + response = @request_client.conn.get do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + unless request_options.nil? || request_options&.additional_body_parameters.nil? + req.body = { **(request_options&.additional_body_parameters || {}) }.compact + end + req.url "#{@request_client.get_url(request_options: request_options)}/user/organizations/#{organization_id}/users/#{user_id}" + end + SeedPathParametersClient::User::User.from_json(json_object: response.body) + end + end + end +end diff --git a/seed/ruby-sdk/path-parameters/lib/fern_path_parameters/user/types/user.rb b/seed/ruby-sdk/path-parameters/lib/fern_path_parameters/user/types/user.rb new file mode 100644 index 00000000000..eb1597e8b5b --- /dev/null +++ b/seed/ruby-sdk/path-parameters/lib/fern_path_parameters/user/types/user.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "ostruct" +require "json" + +module SeedPathParametersClient + class User + class User + # @return [String] + attr_reader :name + # @return [Array] + attr_reader :tags + # @return [OpenStruct] Additional properties unmapped to the current class definition + attr_reader :additional_properties + # @return [Object] + attr_reader :_field_set + protected :_field_set + + OMIT = Object.new + + # @param name [String] + # @param tags [Array] + # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition + # @return [SeedPathParametersClient::User::User] + def initialize(name:, tags:, additional_properties: nil) + @name = name + @tags = tags + @additional_properties = additional_properties + @_field_set = { "name": name, "tags": tags } + end + + # Deserialize a JSON object to an instance of User + # + # @param json_object [String] + # @return [SeedPathParametersClient::User::User] + def self.from_json(json_object:) + struct = JSON.parse(json_object, object_class: OpenStruct) + parsed_json = JSON.parse(json_object) + name = parsed_json["name"] + tags = parsed_json["tags"] + new( + name: name, + tags: tags, + additional_properties: struct + ) + end + + # Serialize an instance of User to a JSON object + # + # @return [String] + def to_json(*_args) + @_field_set&.to_json + end + + # Leveraged for Union-type generation, validate_raw attempts to parse the given + # hash and check each fields type against the current object's property + # definitions. + # + # @param obj [Object] + # @return [Void] + def self.validate_raw(obj:) + obj.name.is_a?(String) != false || raise("Passed value for field obj.name is not the expected type, validation failed.") + obj.tags.is_a?(Array) != false || raise("Passed value for field obj.tags is not the expected type, validation failed.") + end + end + end +end diff --git a/seed/ruby-sdk/path-parameters/lib/gemconfig.rb b/seed/ruby-sdk/path-parameters/lib/gemconfig.rb new file mode 100644 index 00000000000..863f0b60bc6 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/lib/gemconfig.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module SeedPathParametersClient + module Gemconfig + VERSION = "" + AUTHORS = [""].freeze + EMAIL = "" + SUMMARY = "" + DESCRIPTION = "" + HOMEPAGE = "https://github.com/path-parameters/fern" + SOURCE_CODE_URI = "https://github.com/path-parameters/fern" + CHANGELOG_URI = "https://github.com/path-parameters/fern/blob/master/CHANGELOG.md" + end +end diff --git a/seed/ruby-sdk/path-parameters/lib/requests.rb b/seed/ruby-sdk/path-parameters/lib/requests.rb new file mode 100644 index 00000000000..c0a12d56984 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/lib/requests.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "faraday" +require "faraday/retry" +require "async/http/faraday" + +module SeedPathParametersClient + class RequestClient + # @return [Faraday] + attr_reader :conn + # @return [String] + attr_reader :base_url + + # @param base_url [String] + # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. + # @param timeout_in_seconds [Long] + # @return [SeedPathParametersClient::RequestClient] + def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) + @base_url = base_url + @conn = Faraday.new do |faraday| + faraday.request :json + faraday.response :raise_error, include_request: true + faraday.request :retry, { max: max_retries } unless max_retries.nil? + faraday.options.timeout = timeout_in_seconds unless timeout_in_seconds.nil? + end + end + + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [String] + def get_url(request_options: nil) + request_options&.base_url || @base_url + end + + # @return [Hash{String => String}] + def get_headers + { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "fern_path_parameters", "X-Fern-SDK-Version": "0.0.1" } + end + end + + class AsyncRequestClient + # @return [Faraday] + attr_reader :conn + # @return [String] + attr_reader :base_url + + # @param base_url [String] + # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. + # @param timeout_in_seconds [Long] + # @return [SeedPathParametersClient::AsyncRequestClient] + def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) + @base_url = base_url + @conn = Faraday.new do |faraday| + faraday.request :json + faraday.response :raise_error, include_request: true + faraday.adapter :async_http + faraday.request :retry, { max: max_retries } unless max_retries.nil? + faraday.options.timeout = timeout_in_seconds unless timeout_in_seconds.nil? + end + end + + # @param request_options [SeedPathParametersClient::RequestOptions] + # @return [String] + def get_url(request_options: nil) + request_options&.base_url || @base_url + end + + # @return [Hash{String => String}] + def get_headers + { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "fern_path_parameters", "X-Fern-SDK-Version": "0.0.1" } + end + end + + # Additional options for request-specific configuration when calling APIs via the + # SDK. + class RequestOptions + # @return [String] + attr_reader :base_url + # @return [Hash{String => Object}] + attr_reader :additional_headers + # @return [Hash{String => Object}] + attr_reader :additional_query_parameters + # @return [Hash{String => Object}] + attr_reader :additional_body_parameters + # @return [Long] + attr_reader :timeout_in_seconds + + # @param base_url [String] + # @param additional_headers [Hash{String => Object}] + # @param additional_query_parameters [Hash{String => Object}] + # @param additional_body_parameters [Hash{String => Object}] + # @param timeout_in_seconds [Long] + # @return [SeedPathParametersClient::RequestOptions] + def initialize(base_url: nil, additional_headers: nil, additional_query_parameters: nil, + additional_body_parameters: nil, timeout_in_seconds: nil) + @base_url = base_url + @additional_headers = additional_headers + @additional_query_parameters = additional_query_parameters + @additional_body_parameters = additional_body_parameters + @timeout_in_seconds = timeout_in_seconds + end + end + + # Additional options for request-specific configuration when calling APIs via the + # SDK. + class IdempotencyRequestOptions + # @return [String] + attr_reader :base_url + # @return [Hash{String => Object}] + attr_reader :additional_headers + # @return [Hash{String => Object}] + attr_reader :additional_query_parameters + # @return [Hash{String => Object}] + attr_reader :additional_body_parameters + # @return [Long] + attr_reader :timeout_in_seconds + + # @param base_url [String] + # @param additional_headers [Hash{String => Object}] + # @param additional_query_parameters [Hash{String => Object}] + # @param additional_body_parameters [Hash{String => Object}] + # @param timeout_in_seconds [Long] + # @return [SeedPathParametersClient::IdempotencyRequestOptions] + def initialize(base_url: nil, additional_headers: nil, additional_query_parameters: nil, + additional_body_parameters: nil, timeout_in_seconds: nil) + @base_url = base_url + @additional_headers = additional_headers + @additional_query_parameters = additional_query_parameters + @additional_body_parameters = additional_body_parameters + @timeout_in_seconds = timeout_in_seconds + end + end +end diff --git a/seed/ruby-sdk/path-parameters/lib/types_export.rb b/seed/ruby-sdk/path-parameters/lib/types_export.rb new file mode 100644 index 00000000000..ee41ff9121c --- /dev/null +++ b/seed/ruby-sdk/path-parameters/lib/types_export.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require_relative "fern_path_parameters/user/types/user" diff --git a/seed/ruby-sdk/path-parameters/snippet-templates.json b/seed/ruby-sdk/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-sdk/path-parameters/snippet.json b/seed/ruby-sdk/path-parameters/snippet.json new file mode 100644 index 00000000000..1dac4308469 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/snippet.json @@ -0,0 +1,71 @@ +{ + "endpoints": [ + { + "id": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganization" + }, + "snippet": { + "client": "require \"fern_path_parameters\"\n\npath_parameters = SeedPathParametersClient::Client.new(base_url: \"https://api.example.com\")\npath_parameters.user.get_organization(organization_id: \"organizationId\")", + "type": "ruby" + } + }, + { + "id": { + "path": "/user/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getUser" + }, + "snippet": { + "client": "require \"fern_path_parameters\"\n\npath_parameters = SeedPathParametersClient::Client.new(base_url: \"https://api.example.com\")\npath_parameters.user.get_user(user_id: \"userId\")", + "type": "ruby" + } + }, + { + "id": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganizationUser" + }, + "snippet": { + "client": "require \"fern_path_parameters\"\n\npath_parameters = SeedPathParametersClient::Client.new(base_url: \"https://api.example.com\")\npath_parameters.user.get_organization_user(organization_id: \"organizationId\", user_id: \"userId\")", + "type": "ruby" + } + }, + { + "id": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganization" + }, + "snippet": { + "client": "require \"fern_path_parameters\"\n\npath_parameters = SeedPathParametersClient::Client.new(base_url: \"https://api.example.com\")\npath_parameters.user.get_organization(organization_id: \"organizationId\")", + "type": "ruby" + } + }, + { + "id": { + "path": "/user/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getUser" + }, + "snippet": { + "client": "require \"fern_path_parameters\"\n\npath_parameters = SeedPathParametersClient::Client.new(base_url: \"https://api.example.com\")\npath_parameters.user.get_user(user_id: \"userId\")", + "type": "ruby" + } + }, + { + "id": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganizationUser" + }, + "snippet": { + "client": "require \"fern_path_parameters\"\n\npath_parameters = SeedPathParametersClient::Client.new(base_url: \"https://api.example.com\")\npath_parameters.user.get_organization_user(organization_id: \"organizationId\", user_id: \"userId\")", + "type": "ruby" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/ruby-sdk/path-parameters/test/test_fern_path_parameters.rb b/seed/ruby-sdk/path-parameters/test/test_fern_path_parameters.rb new file mode 100644 index 00000000000..cb3c3561dd0 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/test/test_fern_path_parameters.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative "test_helper" +require "fern_path_parameters" + +# Basic SeedPathParametersClient tests +class TestSeedPathParametersClient < Minitest::Test + def test_function + # SeedPathParametersClient::Client.new + end +end diff --git a/seed/ruby-sdk/path-parameters/test/test_helper.rb b/seed/ruby-sdk/path-parameters/test/test_helper.rb new file mode 100644 index 00000000000..c41f2256841 --- /dev/null +++ b/seed/ruby-sdk/path-parameters/test/test_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) + +require "minitest/autorun" +require "fern_path_parameters" diff --git a/seed/ts-express/path-parameters/.mock/definition/api.yml b/seed/ts-express/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/ts-express/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/ts-express/path-parameters/.mock/definition/user.yml b/seed/ts-express/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/ts-express/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/ts-express/path-parameters/.mock/fern.config.json b/seed/ts-express/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ts-express/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ts-express/path-parameters/.mock/generators.yml b/seed/ts-express/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/ts-express/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/ts-express/path-parameters/api/index.ts b/seed/ts-express/path-parameters/api/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-express/path-parameters/api/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-express/path-parameters/api/resources/index.ts b/seed/ts-express/path-parameters/api/resources/index.ts new file mode 100644 index 00000000000..e2e6c212efb --- /dev/null +++ b/seed/ts-express/path-parameters/api/resources/index.ts @@ -0,0 +1,2 @@ +export * as user from "./user"; +export * from "./user/types"; diff --git a/seed/ts-express/path-parameters/api/resources/user/index.ts b/seed/ts-express/path-parameters/api/resources/user/index.ts new file mode 100644 index 00000000000..fcc81debec4 --- /dev/null +++ b/seed/ts-express/path-parameters/api/resources/user/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./service"; diff --git a/seed/ts-express/path-parameters/api/resources/user/service/UserService.ts b/seed/ts-express/path-parameters/api/resources/user/service/UserService.ts new file mode 100644 index 00000000000..842007226f4 --- /dev/null +++ b/seed/ts-express/path-parameters/api/resources/user/service/UserService.ts @@ -0,0 +1,166 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedPathParameters from "../../../index"; +import express from "express"; +import * as serializers from "../../../../serialization/index"; +import * as errors from "../../../../errors/index"; + +export interface UserServiceMethods { + getOrganization( + req: express.Request< + { + organizationId: string; + }, + SeedPathParameters.User, + never, + never + >, + res: { + send: (responseBody: SeedPathParameters.User) => Promise; + cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; + locals: any; + }, + next: express.NextFunction + ): void | Promise; + getUser( + req: express.Request< + { + userId: string; + }, + SeedPathParameters.User, + never, + never + >, + res: { + send: (responseBody: SeedPathParameters.User) => Promise; + cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; + locals: any; + }, + next: express.NextFunction + ): void | Promise; + getOrganizationUser( + req: express.Request< + { + organizationId: string; + userId: string; + }, + SeedPathParameters.User, + never, + never + >, + res: { + send: (responseBody: SeedPathParameters.User) => Promise; + cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; + locals: any; + }, + next: express.NextFunction + ): void | Promise; +} + +export class UserService { + private router; + + constructor(private readonly methods: UserServiceMethods, middleware: express.RequestHandler[] = []) { + this.router = express.Router({ mergeParams: true }).use( + express.json({ + strict: false, + }), + ...middleware + ); + } + + public addMiddleware(handler: express.RequestHandler): this { + this.router.use(handler); + return this; + } + + public toRouter(): express.Router { + this.router.get("/organizations/:organizationId", async (req, res, next) => { + try { + await this.methods.getOrganization( + req as any, + { + send: async (responseBody) => { + res.json(serializers.User.jsonOrThrow(responseBody, { unrecognizedObjectKeys: "strip" })); + }, + cookie: res.cookie.bind(res), + locals: res.locals, + }, + next + ); + next(); + } catch (error) { + if (error instanceof errors.SeedPathParametersError) { + console.warn( + `Endpoint 'getOrganization' unexpectedly threw ${error.constructor.name}.` + + ` If this was intentional, please add ${error.constructor.name} to` + + " the endpoint's errors list in your Fern Definition." + ); + await error.send(res); + } else { + res.status(500).json("Internal Server Error"); + } + next(error); + } + }); + this.router.get("/users/:userId", async (req, res, next) => { + try { + await this.methods.getUser( + req as any, + { + send: async (responseBody) => { + res.json(serializers.User.jsonOrThrow(responseBody, { unrecognizedObjectKeys: "strip" })); + }, + cookie: res.cookie.bind(res), + locals: res.locals, + }, + next + ); + next(); + } catch (error) { + if (error instanceof errors.SeedPathParametersError) { + console.warn( + `Endpoint 'getUser' unexpectedly threw ${error.constructor.name}.` + + ` If this was intentional, please add ${error.constructor.name} to` + + " the endpoint's errors list in your Fern Definition." + ); + await error.send(res); + } else { + res.status(500).json("Internal Server Error"); + } + next(error); + } + }); + this.router.get("/organizations/:organizationId/users/:userId", async (req, res, next) => { + try { + await this.methods.getOrganizationUser( + req as any, + { + send: async (responseBody) => { + res.json(serializers.User.jsonOrThrow(responseBody, { unrecognizedObjectKeys: "strip" })); + }, + cookie: res.cookie.bind(res), + locals: res.locals, + }, + next + ); + next(); + } catch (error) { + if (error instanceof errors.SeedPathParametersError) { + console.warn( + `Endpoint 'getOrganizationUser' unexpectedly threw ${error.constructor.name}.` + + ` If this was intentional, please add ${error.constructor.name} to` + + " the endpoint's errors list in your Fern Definition." + ); + await error.send(res); + } else { + res.status(500).json("Internal Server Error"); + } + next(error); + } + }); + return this.router; + } +} diff --git a/seed/ts-express/path-parameters/api/resources/user/service/index.ts b/seed/ts-express/path-parameters/api/resources/user/service/index.ts new file mode 100644 index 00000000000..cb0ff5c3b54 --- /dev/null +++ b/seed/ts-express/path-parameters/api/resources/user/service/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/seed/ts-express/path-parameters/api/resources/user/types/User.ts b/seed/ts-express/path-parameters/api/resources/user/types/User.ts new file mode 100644 index 00000000000..4e9bf453214 --- /dev/null +++ b/seed/ts-express/path-parameters/api/resources/user/types/User.ts @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface User { + name: string; + tags: string[]; +} diff --git a/seed/ts-express/path-parameters/api/resources/user/types/index.ts b/seed/ts-express/path-parameters/api/resources/user/types/index.ts new file mode 100644 index 00000000000..3ce758c1197 --- /dev/null +++ b/seed/ts-express/path-parameters/api/resources/user/types/index.ts @@ -0,0 +1 @@ +export * from "./User"; diff --git a/seed/ts-express/path-parameters/core/index.ts b/seed/ts-express/path-parameters/core/index.ts new file mode 100644 index 00000000000..3ae53c06d38 --- /dev/null +++ b/seed/ts-express/path-parameters/core/index.ts @@ -0,0 +1 @@ +export * as serialization from "./schemas"; diff --git a/seed/ts-express/path-parameters/core/schemas/Schema.ts b/seed/ts-express/path-parameters/core/schemas/Schema.ts new file mode 100644 index 00000000000..2a72eacec99 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/Schema.ts @@ -0,0 +1,99 @@ +import { SchemaUtils } from "./builders"; + +export type Schema = BaseSchema & SchemaUtils; + +export type inferRaw = S extends Schema ? Raw : never; +export type inferParsed = S extends Schema ? Parsed : never; + +export interface BaseSchema { + parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; + json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; + getType: () => SchemaType | SchemaType; +} + +export const SchemaType = { + BIGINT: "bigint", + DATE: "date", + ENUM: "enum", + LIST: "list", + STRING_LITERAL: "stringLiteral", + BOOLEAN_LITERAL: "booleanLiteral", + OBJECT: "object", + ANY: "any", + BOOLEAN: "boolean", + NUMBER: "number", + STRING: "string", + UNKNOWN: "unknown", + RECORD: "record", + SET: "set", + UNION: "union", + UNDISCRIMINATED_UNION: "undiscriminatedUnion", + OPTIONAL: "optional", +} as const; +export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; + +export type MaybeValid = Valid | Invalid; + +export interface Valid { + ok: true; + value: T; +} + +export interface Invalid { + ok: false; + errors: ValidationError[]; +} + +export interface ValidationError { + path: string[]; + message: string; +} + +export interface SchemaOptions { + /** + * how to handle unrecognized keys in objects + * + * @default "fail" + */ + unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; + + /** + * whether to fail when an unrecognized discriminant value is + * encountered in a union + * + * @default false + */ + allowUnrecognizedUnionMembers?: boolean; + + /** + * whether to fail when an unrecognized enum value is encountered + * + * @default false + */ + allowUnrecognizedEnumValues?: boolean; + + /** + * whether to allow data that doesn't conform to the schema. + * invalid data is passed through without transformation. + * + * when this is enabled, .parse() and .json() will always + * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` + * will never fail. + * + * @default false + */ + skipValidation?: boolean; + + /** + * each validation failure contains a "path" property, which is + * the breadcrumbs to the offending node in the JSON. you can supply + * a prefix that is prepended to all the errors' paths. this can be + * helpful for zurg's internal debug logging. + */ + breadcrumbsPrefix?: string[]; + + /** + * whether to send 'null' for optional properties explicitly set to 'undefined'. + */ + omitUndefined?: boolean; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/bigint/bigint.ts b/seed/ts-express/path-parameters/core/schemas/builders/bigint/bigint.ts new file mode 100644 index 00000000000..dc9c742e007 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/bigint/bigint.ts @@ -0,0 +1,50 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function bigint(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + return { + ok: true, + value: BigInt(raw), + }; + }, + json: (bigint, { breadcrumbsPrefix = [] } = {}) => { + if (typeof bigint === "bigint") { + return { + ok: true, + value: bigint.toString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(bigint, "bigint"), + }, + ], + }; + } + }, + getType: () => SchemaType.BIGINT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/bigint/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/bigint/index.ts new file mode 100644 index 00000000000..e5843043fcb --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/bigint/index.ts @@ -0,0 +1 @@ +export { bigint } from "./bigint"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/date/date.ts b/seed/ts-express/path-parameters/core/schemas/builders/date/date.ts new file mode 100644 index 00000000000..b70f24b045a --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/date/date.ts @@ -0,0 +1,65 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +// https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime +const ISO_8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + +export function date(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + if (!ISO_8601_REGEX.test(raw)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), + }, + ], + }; + } + return { + ok: true, + value: new Date(raw), + }; + }, + json: (date, { breadcrumbsPrefix = [] } = {}) => { + if (date instanceof Date) { + return { + ok: true, + value: date.toISOString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(date, "Date object"), + }, + ], + }; + } + }, + getType: () => SchemaType.DATE, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/date/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/date/index.ts new file mode 100644 index 00000000000..187b29040f6 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/date/index.ts @@ -0,0 +1 @@ +export { date } from "./date"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/enum/enum.ts b/seed/ts-express/path-parameters/core/schemas/builders/enum/enum.ts new file mode 100644 index 00000000000..c1e24d69dec --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/enum/enum.ts @@ -0,0 +1,43 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function enum_(values: E): Schema { + const validValues = new Set(values); + + const schemaCreator = createIdentitySchemaCreator( + SchemaType.ENUM, + (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { + if (typeof value !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + + if (!validValues.has(value) && !allowUnrecognizedEnumValues) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "enum"), + }, + ], + }; + } + + return { + ok: true, + value: value as U, + }; + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/enum/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/enum/index.ts new file mode 100644 index 00000000000..fe6faed93e3 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/enum/index.ts @@ -0,0 +1 @@ +export { enum_ } from "./enum"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/index.ts new file mode 100644 index 00000000000..65211f92522 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/index.ts @@ -0,0 +1,14 @@ +export * from "./bigint"; +export * from "./date"; +export * from "./enum"; +export * from "./lazy"; +export * from "./list"; +export * from "./literals"; +export * from "./object"; +export * from "./object-like"; +export * from "./primitives"; +export * from "./record"; +export * from "./schema-utils"; +export * from "./set"; +export * from "./undiscriminated-union"; +export * from "./union"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/lazy/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/lazy/index.ts new file mode 100644 index 00000000000..77420fb031c --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/lazy/index.ts @@ -0,0 +1,3 @@ +export { lazy } from "./lazy"; +export type { SchemaGetter } from "./lazy"; +export { lazyObject } from "./lazyObject"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/lazy/lazy.ts b/seed/ts-express/path-parameters/core/schemas/builders/lazy/lazy.ts new file mode 100644 index 00000000000..835c61f8a56 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/lazy/lazy.ts @@ -0,0 +1,32 @@ +import { BaseSchema, Schema } from "../../Schema"; +import { getSchemaUtils } from "../schema-utils"; + +export type SchemaGetter> = () => SchemaType; + +export function lazy(getter: SchemaGetter>): Schema { + const baseSchema = constructLazyBaseSchema(getter); + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function constructLazyBaseSchema( + getter: SchemaGetter> +): BaseSchema { + return { + parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), + json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), + getType: () => getMemoizedSchema(getter).getType(), + }; +} + +type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; + +export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { + const castedGetter = getter as MemoizedGetter; + if (castedGetter.__zurg_memoized == null) { + castedGetter.__zurg_memoized = getter(); + } + return castedGetter.__zurg_memoized; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/lazy/lazyObject.ts b/seed/ts-express/path-parameters/core/schemas/builders/lazy/lazyObject.ts new file mode 100644 index 00000000000..38c9e28404b --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/lazy/lazyObject.ts @@ -0,0 +1,20 @@ +import { getObjectUtils } from "../object"; +import { getObjectLikeUtils } from "../object-like"; +import { BaseObjectSchema, ObjectSchema } from "../object/types"; +import { getSchemaUtils } from "../schema-utils"; +import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; + +export function lazyObject(getter: SchemaGetter>): ObjectSchema { + const baseSchema: BaseObjectSchema = { + ...constructLazyBaseSchema(getter), + _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), + _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/list/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/list/index.ts new file mode 100644 index 00000000000..25f4bcc1737 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/list/index.ts @@ -0,0 +1 @@ +export { list } from "./list"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/list/list.ts b/seed/ts-express/path-parameters/core/schemas/builders/list/list.ts new file mode 100644 index 00000000000..e4c5c4a4a99 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/list/list.ts @@ -0,0 +1,73 @@ +import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function list(schema: Schema): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => + validateAndTransformArray(raw, (item, index) => + schema.parse(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + json: (parsed, opts) => + validateAndTransformArray(parsed, (item, index) => + schema.json(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + getType: () => SchemaType.LIST, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformArray( + value: unknown, + transformItem: (item: Raw, index: number) => MaybeValid +): MaybeValid { + if (!Array.isArray(value)) { + return { + ok: false, + errors: [ + { + message: getErrorMessageForIncorrectType(value, "list"), + path: [], + }, + ], + }; + } + + const maybeValidItems = value.map((item, index) => transformItem(item, index)); + + return maybeValidItems.reduce>( + (acc, item) => { + if (acc.ok && item.ok) { + return { + ok: true, + value: [...acc.value, item.value], + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!item.ok) { + errors.push(...item.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: [] } + ); +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/literals/booleanLiteral.ts b/seed/ts-express/path-parameters/core/schemas/builders/literals/booleanLiteral.ts new file mode 100644 index 00000000000..a83d22cd48a --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/literals/booleanLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function booleanLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.BOOLEAN_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/literals/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/literals/index.ts new file mode 100644 index 00000000000..d2bf08fc6ca --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/literals/index.ts @@ -0,0 +1,2 @@ +export { stringLiteral } from "./stringLiteral"; +export { booleanLiteral } from "./booleanLiteral"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/literals/stringLiteral.ts b/seed/ts-express/path-parameters/core/schemas/builders/literals/stringLiteral.ts new file mode 100644 index 00000000000..3939b76b48d --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/literals/stringLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function stringLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.STRING_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `"${literal}"`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/path-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts new file mode 100644 index 00000000000..8331d08da89 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -0,0 +1,79 @@ +import { BaseSchema } from "../../Schema"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { getSchemaUtils } from "../schema-utils"; +import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; + +export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { + return { + withParsedProperties: (properties) => withParsedProperties(schema, properties), + }; +} + +/** + * object-like utils are defined in one file to resolve issues with circular imports + */ + +export function withParsedProperties( + objectLike: BaseSchema, + properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } +): ObjectLikeSchema { + const objectSchema: BaseSchema = { + parse: (raw, opts) => { + const parsedObject = objectLike.parse(raw, opts); + if (!parsedObject.ok) { + return parsedObject; + } + + const additionalProperties = Object.entries(properties).reduce>( + (processed, [key, value]) => { + return { + ...processed, + [key]: typeof value === "function" ? value(parsedObject.value) : value, + }; + }, + {} + ); + + return { + ok: true, + value: { + ...parsedObject.value, + ...(additionalProperties as Properties), + }, + }; + }, + + json: (parsed, opts) => { + if (!isPlainObject(parsed)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "object"), + }, + ], + }; + } + + // strip out added properties + const addedPropertyKeys = new Set(Object.keys(properties)); + const parsedWithoutAddedProperties = filterObject( + parsed, + Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) + ); + + return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); + }, + + getType: () => objectLike.getType(), + }; + + return { + ...objectSchema, + ...getSchemaUtils(objectSchema), + ...getObjectLikeUtils(objectSchema), + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object-like/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/object-like/index.ts new file mode 100644 index 00000000000..c342e72cf9d --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object-like/index.ts @@ -0,0 +1,2 @@ +export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; +export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object-like/types.ts b/seed/ts-express/path-parameters/core/schemas/builders/object-like/types.ts new file mode 100644 index 00000000000..75b3698729c --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object-like/types.ts @@ -0,0 +1,11 @@ +import { BaseSchema, Schema } from "../../Schema"; + +export type ObjectLikeSchema = Schema & + BaseSchema & + ObjectLikeUtils; + +export interface ObjectLikeUtils { + withParsedProperties: >(properties: { + [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); + }) => ObjectLikeSchema; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/object/index.ts new file mode 100644 index 00000000000..e3f4388db28 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object/index.ts @@ -0,0 +1,22 @@ +export { getObjectUtils, object } from "./object"; +export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; +export type { + inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, +} from "./objectWithoutOptionalProperties"; +export { isProperty, property } from "./property"; +export type { Property } from "./property"; +export type { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObject, + inferParsedObjectFromPropertySchemas, + inferParsedPropertySchema, + inferRawKey, + inferRawObject, + inferRawObjectFromPropertySchemas, + inferRawPropertySchema, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object/object.ts b/seed/ts-express/path-parameters/core/schemas/builders/object/object.ts new file mode 100644 index 00000000000..e00136d72fc --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object/object.ts @@ -0,0 +1,324 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { partition } from "../../utils/partition"; +import { getObjectLikeUtils } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { isProperty } from "./property"; +import { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObjectFromPropertySchemas, + inferRawObjectFromPropertySchemas, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; + +interface ObjectPropertyWithRawKey { + rawKey: string; + parsedKey: string; + valueSchema: Schema; +} + +export function object>( + schemas: T +): inferObjectSchemaFromPropertySchemas { + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const rawKeyToProperty: Record = {}; + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + const property: ObjectPropertyWithRawKey = { + rawKey, + parsedKey: parsedKey as string, + valueSchema, + }; + + rawKeyToProperty[rawKey] = property; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(rawKey); + } + } + + return validateAndTransformObject({ + value: raw, + requiredKeys, + getProperty: (rawKey) => { + const property = rawKeyToProperty[rawKey]; + if (property == null) { + return undefined; + } + return { + transformedKey: property.parsedKey, + transform: (propertyValue) => + property.valueSchema.parse(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], + }), + }; + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + json: (parsed, opts) => { + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(parsedKey as string); + } + } + + return validateAndTransformObject({ + value: parsed, + requiredKeys, + getProperty: ( + parsedKey + ): { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined => { + const property = schemas[parsedKey as keyof T]; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property == null) { + return undefined; + } + + if (isProperty(property)) { + return { + transformedKey: property.rawKey, + transform: (propertyValue) => + property.valueSchema.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } else { + return { + transformedKey: parsedKey, + transform: (propertyValue) => + property.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + getType: () => SchemaType.OBJECT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} + +function validateAndTransformObject({ + value, + requiredKeys, + getProperty, + unrecognizedObjectKeys = "fail", + skipValidation = false, + breadcrumbsPrefix = [], +}: { + value: unknown; + requiredKeys: string[]; + getProperty: ( + preTransformedKey: string + ) => { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined; + unrecognizedObjectKeys: "fail" | "passthrough" | "strip" | undefined; + skipValidation: boolean | undefined; + breadcrumbsPrefix: string[] | undefined; + omitUndefined: boolean | undefined; +}): MaybeValid { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const missingRequiredKeys = new Set(requiredKeys); + const errors: ValidationError[] = []; + const transformed: Record = {}; + + for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + const property = getProperty(preTransformedKey); + + if (property != null) { + missingRequiredKeys.delete(preTransformedKey); + + const value = property.transform(preTransformedItemValue); + if (value.ok) { + transformed[property.transformedKey] = value.value; + } else { + transformed[preTransformedKey] = preTransformedItemValue; + errors.push(...value.errors); + } + } else { + switch (unrecognizedObjectKeys) { + case "fail": + errors.push({ + path: [...breadcrumbsPrefix, preTransformedKey], + message: `Unexpected key "${preTransformedKey}"`, + }); + break; + case "strip": + break; + case "passthrough": + transformed[preTransformedKey] = preTransformedItemValue; + break; + } + } + } + + errors.push( + ...requiredKeys + .filter((key) => missingRequiredKeys.has(key)) + .map((key) => ({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + })) + ); + + if (errors.length === 0 || skipValidation) { + return { + ok: true, + value: transformed as Transformed, + }; + } else { + return { + ok: false, + errors, + }; + } +} + +export function getObjectUtils(schema: BaseObjectSchema): ObjectUtils { + return { + extend: (extension: ObjectSchema) => { + const baseSchema: BaseObjectSchema = { + _getParsedProperties: () => [...schema._getParsedProperties(), ...extension._getParsedProperties()], + _getRawProperties: () => [...schema._getRawProperties(), ...extension._getRawProperties()], + parse: (raw, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getRawProperties(), + value: raw, + transformBase: (rawBase) => schema.parse(rawBase, opts), + transformExtension: (rawExtension) => extension.parse(rawExtension, opts), + }); + }, + json: (parsed, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getParsedProperties(), + value: parsed, + transformBase: (parsedBase) => schema.json(parsedBase, opts), + transformExtension: (parsedExtension) => extension.json(parsedExtension, opts), + }); + }, + getType: () => SchemaType.OBJECT, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; + }, + }; +} + +function validateAndTransformExtendedObject({ + extensionKeys, + value, + transformBase, + transformExtension, +}: { + extensionKeys: (keyof PreTransformedExtension)[]; + value: unknown; + transformBase: (value: unknown) => MaybeValid; + transformExtension: (value: unknown) => MaybeValid; +}): MaybeValid { + const extensionPropertiesSet = new Set(extensionKeys); + const [extensionProperties, baseProperties] = partition(keys(value), (key) => + extensionPropertiesSet.has(key as keyof PreTransformedExtension) + ); + + const transformedBase = transformBase(filterObject(value, baseProperties)); + const transformedExtension = transformExtension(filterObject(value, extensionProperties)); + + if (transformedBase.ok && transformedExtension.ok) { + return { + ok: true, + value: { + ...transformedBase.value, + ...transformedExtension.value, + }, + }; + } else { + return { + ok: false, + errors: [ + ...(transformedBase.ok ? [] : transformedBase.errors), + ...(transformedExtension.ok ? [] : transformedExtension.errors), + ], + }; + } +} + +function isSchemaRequired(schema: Schema): boolean { + return !isSchemaOptional(schema); +} + +function isSchemaOptional(schema: Schema): boolean { + switch (schema.getType()) { + case SchemaType.ANY: + case SchemaType.UNKNOWN: + case SchemaType.OPTIONAL: + return true; + default: + return false; + } +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object/objectWithoutOptionalProperties.ts b/seed/ts-express/path-parameters/core/schemas/builders/object/objectWithoutOptionalProperties.ts new file mode 100644 index 00000000000..a0951f48efc --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object/objectWithoutOptionalProperties.ts @@ -0,0 +1,18 @@ +import { object } from "./object"; +import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; + +export function objectWithoutOptionalProperties>( + schemas: T +): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { + return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; +} + +export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = + ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas + >; + +export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { + [K in keyof T]: inferParsedPropertySchema; +}; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object/property.ts b/seed/ts-express/path-parameters/core/schemas/builders/object/property.ts new file mode 100644 index 00000000000..d245c4b193a --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object/property.ts @@ -0,0 +1,23 @@ +import { Schema } from "../../Schema"; + +export function property( + rawKey: RawKey, + valueSchema: Schema +): Property { + return { + rawKey, + valueSchema, + isProperty: true, + }; +} + +export interface Property { + rawKey: RawKey; + valueSchema: Schema; + isProperty: true; +} + +export function isProperty>(maybeProperty: unknown): maybeProperty is O { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (maybeProperty as O).isProperty; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object/types.ts b/seed/ts-express/path-parameters/core/schemas/builders/object/types.ts new file mode 100644 index 00000000000..de9bb4074e5 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/object/types.ts @@ -0,0 +1,72 @@ +import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; +import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; +import { ObjectLikeUtils } from "../object-like"; +import { SchemaUtils } from "../schema-utils"; +import { Property } from "./property"; + +export type ObjectSchema = BaseObjectSchema & + ObjectLikeUtils & + ObjectUtils & + SchemaUtils; + +export interface BaseObjectSchema extends BaseSchema { + _getRawProperties: () => (keyof Raw)[]; + _getParsedProperties: () => (keyof Parsed)[]; +} + +export interface ObjectUtils { + extend: ( + schemas: ObjectSchema + ) => ObjectSchema; +} + +export type inferRawObject> = O extends ObjectSchema ? Raw : never; + +export type inferParsedObject> = O extends ObjectSchema + ? Parsed + : never; + +export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas +>; + +export type inferRawObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; + }>; + +export type inferParsedObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [K in keyof T]: inferParsedPropertySchema; + }>; + +export type PropertySchemas = Record< + ParsedKeys, + Property | Schema +>; + +export type inferRawPropertySchema

| Schema> = P extends Property< + any, + infer Raw, + any +> + ? Raw + : P extends Schema + ? inferRaw

+ : never; + +export type inferParsedPropertySchema

| Schema> = P extends Property< + any, + any, + infer Parsed +> + ? Parsed + : P extends Schema + ? inferParsed

+ : never; + +export type inferRawKey< + ParsedKey extends string | number | symbol, + P extends Property | Schema +> = P extends Property ? Raw : ParsedKey; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/primitives/any.ts b/seed/ts-express/path-parameters/core/schemas/builders/primitives/any.ts new file mode 100644 index 00000000000..fcaeb04255a --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/primitives/any.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); diff --git a/seed/ts-express/path-parameters/core/schemas/builders/primitives/boolean.ts b/seed/ts-express/path-parameters/core/schemas/builders/primitives/boolean.ts new file mode 100644 index 00000000000..fad60562120 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/primitives/boolean.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const boolean = createIdentitySchemaCreator( + SchemaType.BOOLEAN, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "boolean") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "boolean"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/path-parameters/core/schemas/builders/primitives/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/primitives/index.ts new file mode 100644 index 00000000000..788f9416bfe --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/primitives/index.ts @@ -0,0 +1,5 @@ +export { any } from "./any"; +export { boolean } from "./boolean"; +export { number } from "./number"; +export { string } from "./string"; +export { unknown } from "./unknown"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/primitives/number.ts b/seed/ts-express/path-parameters/core/schemas/builders/primitives/number.ts new file mode 100644 index 00000000000..c2689456936 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/primitives/number.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const number = createIdentitySchemaCreator( + SchemaType.NUMBER, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "number") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "number"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/path-parameters/core/schemas/builders/primitives/string.ts b/seed/ts-express/path-parameters/core/schemas/builders/primitives/string.ts new file mode 100644 index 00000000000..949f1f2a630 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/primitives/string.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const string = createIdentitySchemaCreator( + SchemaType.STRING, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "string") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/path-parameters/core/schemas/builders/primitives/unknown.ts b/seed/ts-express/path-parameters/core/schemas/builders/primitives/unknown.ts new file mode 100644 index 00000000000..4d5249571f5 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/primitives/unknown.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); diff --git a/seed/ts-express/path-parameters/core/schemas/builders/record/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/record/index.ts new file mode 100644 index 00000000000..82e25c5c2af --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/record/index.ts @@ -0,0 +1,2 @@ +export { record } from "./record"; +export type { BaseRecordSchema, RecordSchema } from "./types"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/record/record.ts b/seed/ts-express/path-parameters/core/schemas/builders/record/record.ts new file mode 100644 index 00000000000..6683ac3609f --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/record/record.ts @@ -0,0 +1,130 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { BaseRecordSchema, RecordSchema } from "./types"; + +export function record( + keySchema: Schema, + valueSchema: Schema +): RecordSchema { + const baseSchema: BaseRecordSchema = { + parse: (raw, opts) => { + return validateAndTransformRecord({ + value: raw, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.parse(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.parse(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return validateAndTransformRecord({ + value: parsed, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.json(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.json(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.RECORD, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformRecord({ + value, + isKeyNumeric, + transformKey, + transformValue, + breadcrumbsPrefix = [], +}: { + value: unknown; + isKeyNumeric: boolean; + transformKey: (key: string | number) => MaybeValid; + transformValue: (value: unknown, key: string | number) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + return entries(value).reduce>>( + (accPromise, [stringKey, value]) => { + // skip nullish keys + if (value == null) { + return accPromise; + } + + const acc = accPromise; + + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!isNaN(numberKey)) { + key = numberKey; + } + } + const transformedKey = transformKey(key); + + const transformedValue = transformValue(value, key); + + if (acc.ok && transformedKey.ok && transformedValue.ok) { + return { + ok: true, + value: { + ...acc.value, + [transformedKey.value]: transformedValue.value, + }, + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!transformedKey.ok) { + errors.push(...transformedKey.errors); + } + if (!transformedValue.ok) { + errors.push(...transformedValue.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: {} as Record } + ); +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/record/types.ts b/seed/ts-express/path-parameters/core/schemas/builders/record/types.ts new file mode 100644 index 00000000000..eb82cc7f65c --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/record/types.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from "../../Schema"; +import { SchemaUtils } from "../schema-utils"; + +export type RecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseRecordSchema & + SchemaUtils, Record>; + +export type BaseRecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseSchema, Record>; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/JsonError.ts b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/JsonError.ts new file mode 100644 index 00000000000..2b89ca0e7ad --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/JsonError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class JsonError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, JsonError.prototype); + } +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/ParseError.ts b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/ParseError.ts new file mode 100644 index 00000000000..d056eb45cf7 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/ParseError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class ParseError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, ParseError.prototype); + } +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/getSchemaUtils.ts b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/getSchemaUtils.ts new file mode 100644 index 00000000000..79ecad92132 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/getSchemaUtils.ts @@ -0,0 +1,105 @@ +import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; +import { JsonError } from "./JsonError"; +import { ParseError } from "./ParseError"; + +export interface SchemaUtils { + optional: () => Schema; + transform: (transformer: SchemaTransformer) => Schema; + parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; + jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; +} + +export interface SchemaTransformer { + transform: (parsed: Parsed) => Transformed; + untransform: (transformed: any) => Parsed; +} + +export function getSchemaUtils(schema: BaseSchema): SchemaUtils { + return { + optional: () => optional(schema), + transform: (transformer) => transform(schema, transformer), + parseOrThrow: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (parsed.ok) { + return parsed.value; + } + throw new ParseError(parsed.errors); + }, + jsonOrThrow: (parsed, opts) => { + const raw = schema.json(parsed, opts); + if (raw.ok) { + return raw.value; + } + throw new JsonError(raw.errors); + }, + }; +} + +/** + * schema utils are defined in one file to resolve issues with circular imports + */ + +export function optional( + schema: BaseSchema +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + if (raw == null) { + return { + ok: true, + value: undefined, + }; + } + return schema.parse(raw, opts); + }, + json: (parsed, opts) => { + if (opts?.omitUndefined && parsed === undefined) { + return { + ok: true, + value: undefined, + }; + } + if (parsed == null) { + return { + ok: true, + value: null, + }; + } + return schema.json(parsed, opts); + }, + getType: () => SchemaType.OPTIONAL, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function transform( + schema: BaseSchema, + transformer: SchemaTransformer +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (!parsed.ok) { + return parsed; + } + return { + ok: true, + value: transformer.transform(parsed.value), + }; + }, + json: (transformed, opts) => { + const parsed = transformer.untransform(transformed); + return schema.json(parsed, opts); + }, + getType: () => schema.getType(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/index.ts new file mode 100644 index 00000000000..aa04e051dfa --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/index.ts @@ -0,0 +1,4 @@ +export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; +export type { SchemaUtils } from "./getSchemaUtils"; +export { JsonError } from "./JsonError"; +export { ParseError } from "./ParseError"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/stringifyValidationErrors.ts b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/stringifyValidationErrors.ts new file mode 100644 index 00000000000..4160f0a2617 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/schema-utils/stringifyValidationErrors.ts @@ -0,0 +1,8 @@ +import { ValidationError } from "../../Schema"; + +export function stringifyValidationError(error: ValidationError): string { + if (error.path.length === 0) { + return error.message; + } + return `${error.path.join(" -> ")}: ${error.message}`; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/set/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/set/index.ts new file mode 100644 index 00000000000..f3310e8bdad --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/set/index.ts @@ -0,0 +1 @@ +export { set } from "./set"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/set/set.ts b/seed/ts-express/path-parameters/core/schemas/builders/set/set.ts new file mode 100644 index 00000000000..e9e6bb7e539 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/set/set.ts @@ -0,0 +1,43 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { list } from "../list"; +import { getSchemaUtils } from "../schema-utils"; + +export function set(schema: Schema): Schema> { + const listSchema = list(schema); + const baseSchema: BaseSchema> = { + parse: (raw, opts) => { + const parsedList = listSchema.parse(raw, opts); + if (parsedList.ok) { + return { + ok: true, + value: new Set(parsedList.value), + }; + } else { + return parsedList; + } + }, + json: (parsed, opts) => { + if (!(parsed instanceof Set)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "Set"), + }, + ], + }; + } + const jsonList = listSchema.json([...parsed], opts); + return jsonList; + }, + getType: () => SchemaType.SET, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/index.ts new file mode 100644 index 00000000000..75b71cb3565 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/index.ts @@ -0,0 +1,6 @@ +export type { + inferParsedUnidiscriminatedUnionSchema, + inferRawUnidiscriminatedUnionSchema, + UndiscriminatedUnionSchema, +} from "./types"; +export { undiscriminatedUnion } from "./undiscriminatedUnion"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/types.ts b/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/types.ts new file mode 100644 index 00000000000..43e7108a060 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/types.ts @@ -0,0 +1,10 @@ +import { inferParsed, inferRaw, Schema } from "../../Schema"; + +export type UndiscriminatedUnionSchema = Schema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema +>; + +export type inferRawUnidiscriminatedUnionSchema = inferRaw; + +export type inferParsedUnidiscriminatedUnionSchema = inferParsed; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts b/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts new file mode 100644 index 00000000000..21ed3df0f40 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts @@ -0,0 +1,60 @@ +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; + +export function undiscriminatedUnion, ...Schema[]]>( + schemas: Schemas +): Schema, inferParsedUnidiscriminatedUnionSchema> { + const baseSchema: BaseSchema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema + > = { + parse: (raw, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.parse(raw, opts), + schemas, + opts + ); + }, + json: (parsed, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.json(parsed, opts), + schemas, + opts + ); + }, + getType: () => SchemaType.UNDISCRIMINATED_UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformUndiscriminatedUnion( + transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, + schemas: Schema[], + opts: SchemaOptions | undefined +): MaybeValid { + const errors: ValidationError[] = []; + for (const [index, schema] of schemas.entries()) { + const transformed = transform(schema, { ...opts, skipValidation: false }); + if (transformed.ok) { + return transformed; + } else { + for (const error of transformed.errors) { + errors.push({ + path: error.path, + message: `[Variant ${index}] ${error.message}`, + }); + } + } + } + + return { + ok: false, + errors, + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/union/discriminant.ts b/seed/ts-express/path-parameters/core/schemas/builders/union/discriminant.ts new file mode 100644 index 00000000000..55065bc8946 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/union/discriminant.ts @@ -0,0 +1,14 @@ +export function discriminant( + parsedDiscriminant: ParsedDiscriminant, + rawDiscriminant: RawDiscriminant +): Discriminant { + return { + parsedDiscriminant, + rawDiscriminant, + }; +} + +export interface Discriminant { + parsedDiscriminant: ParsedDiscriminant; + rawDiscriminant: RawDiscriminant; +} diff --git a/seed/ts-express/path-parameters/core/schemas/builders/union/index.ts b/seed/ts-express/path-parameters/core/schemas/builders/union/index.ts new file mode 100644 index 00000000000..85fc008a2d8 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/union/index.ts @@ -0,0 +1,10 @@ +export { discriminant } from "./discriminant"; +export type { Discriminant } from "./discriminant"; +export type { + inferParsedDiscriminant, + inferParsedUnion, + inferRawDiscriminant, + inferRawUnion, + UnionSubtypes, +} from "./types"; +export { union } from "./union"; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/union/types.ts b/seed/ts-express/path-parameters/core/schemas/builders/union/types.ts new file mode 100644 index 00000000000..6f82c868b2d --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/union/types.ts @@ -0,0 +1,26 @@ +import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; +import { Discriminant } from "./discriminant"; + +export type UnionSubtypes = { + [K in DiscriminantValues]: ObjectSchema; +}; + +export type inferRawUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferRawObject; +}[keyof U]; + +export type inferParsedUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferParsedObject; +}[keyof U]; + +export type inferRawDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Raw + : never; + +export type inferParsedDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Parsed + : never; diff --git a/seed/ts-express/path-parameters/core/schemas/builders/union/union.ts b/seed/ts-express/path-parameters/core/schemas/builders/union/union.ts new file mode 100644 index 00000000000..ab61475a572 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/builders/union/union.ts @@ -0,0 +1,170 @@ +import { BaseSchema, MaybeValid, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { enum_ } from "../enum"; +import { ObjectSchema } from "../object"; +import { getObjectLikeUtils, ObjectLikeSchema } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { Discriminant } from "./discriminant"; +import { inferParsedDiscriminant, inferParsedUnion, inferRawDiscriminant, inferRawUnion, UnionSubtypes } from "./types"; + +export function union, U extends UnionSubtypes>( + discriminant: D, + union: U +): ObjectLikeSchema, inferParsedUnion> { + const rawDiscriminant = + typeof discriminant === "string" ? discriminant : (discriminant.rawDiscriminant as inferRawDiscriminant); + const parsedDiscriminant = + typeof discriminant === "string" + ? discriminant + : (discriminant.parsedDiscriminant as inferParsedDiscriminant); + + const discriminantValueSchema = enum_(keys(union) as string[]); + + const baseSchema: BaseSchema, inferParsedUnion> = { + parse: (raw, opts) => { + return transformAndValidateUnion({ + value: raw, + discriminant: rawDiscriminant, + transformedDiscriminant: parsedDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.parse(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.parse(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return transformAndValidateUnion({ + value: parsed, + discriminant: parsedDiscriminant, + transformedDiscriminant: rawDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.json(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.json(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + }; +} + +function transformAndValidateUnion< + TransformedDiscriminant extends string, + TransformedDiscriminantValue extends string, + TransformedAdditionalProperties +>({ + value, + discriminant, + transformedDiscriminant, + transformDiscriminantValue, + getAdditionalPropertiesSchema, + allowUnrecognizedUnionMembers = false, + transformAdditionalProperties, + breadcrumbsPrefix = [], +}: { + value: unknown; + discriminant: string; + transformedDiscriminant: TransformedDiscriminant; + transformDiscriminantValue: (discriminantValue: unknown) => MaybeValid; + getAdditionalPropertiesSchema: (discriminantValue: string) => ObjectSchema | undefined; + allowUnrecognizedUnionMembers: boolean | undefined; + transformAdditionalProperties: ( + additionalProperties: unknown, + additionalPropertiesSchema: ObjectSchema + ) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid & TransformedAdditionalProperties> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const { [discriminant]: discriminantValue, ...additionalProperties } = value; + + if (discriminantValue == null) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: `Missing discriminant ("${discriminant}")`, + }, + ], + }; + } + + const transformedDiscriminantValue = transformDiscriminantValue(discriminantValue); + if (!transformedDiscriminantValue.ok) { + return { + ok: false, + errors: transformedDiscriminantValue.errors, + }; + } + + const additionalPropertiesSchema = getAdditionalPropertiesSchema(transformedDiscriminantValue.value); + + if (additionalPropertiesSchema == null) { + if (allowUnrecognizedUnionMembers) { + return { + ok: true, + value: { + [transformedDiscriminant]: transformedDiscriminantValue.value, + ...additionalProperties, + } as Record & TransformedAdditionalProperties, + }; + } else { + return { + ok: false, + errors: [ + { + path: [...breadcrumbsPrefix, discriminant], + message: "Unexpected discriminant value", + }, + ], + }; + } + } + + const transformedAdditionalProperties = transformAdditionalProperties( + additionalProperties, + additionalPropertiesSchema + ); + if (!transformedAdditionalProperties.ok) { + return transformedAdditionalProperties; + } + + return { + ok: true, + value: { + [transformedDiscriminant]: discriminantValue, + ...transformedAdditionalProperties.value, + } as Record & TransformedAdditionalProperties, + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/index.ts b/seed/ts-express/path-parameters/core/schemas/index.ts new file mode 100644 index 00000000000..5429d8b43eb --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./builders"; +export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; diff --git a/seed/ts-express/path-parameters/core/schemas/utils/MaybePromise.ts b/seed/ts-express/path-parameters/core/schemas/utils/MaybePromise.ts new file mode 100644 index 00000000000..9cd354b3418 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/MaybePromise.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise; diff --git a/seed/ts-express/path-parameters/core/schemas/utils/addQuestionMarksToNullableProperties.ts b/seed/ts-express/path-parameters/core/schemas/utils/addQuestionMarksToNullableProperties.ts new file mode 100644 index 00000000000..4111d703cd0 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/addQuestionMarksToNullableProperties.ts @@ -0,0 +1,15 @@ +export type addQuestionMarksToNullableProperties = { + [K in OptionalKeys]?: T[K]; +} & Pick>; + +export type OptionalKeys = { + [K in keyof T]-?: undefined extends T[K] + ? K + : null extends T[K] + ? K + : 1 extends (any extends T[K] ? 0 : 1) + ? never + : K; +}[keyof T]; + +export type RequiredKeys = Exclude>; diff --git a/seed/ts-express/path-parameters/core/schemas/utils/createIdentitySchemaCreator.ts b/seed/ts-express/path-parameters/core/schemas/utils/createIdentitySchemaCreator.ts new file mode 100644 index 00000000000..de107cf5ee1 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/createIdentitySchemaCreator.ts @@ -0,0 +1,21 @@ +import { getSchemaUtils } from "../builders/schema-utils"; +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; +import { maybeSkipValidation } from "./maybeSkipValidation"; + +export function createIdentitySchemaCreator( + schemaType: SchemaType, + validate: (value: unknown, opts?: SchemaOptions) => MaybeValid +): () => Schema { + return () => { + const baseSchema: BaseSchema = { + parse: validate, + json: validate, + getType: () => schemaType, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/utils/entries.ts b/seed/ts-express/path-parameters/core/schemas/utils/entries.ts new file mode 100644 index 00000000000..e122952137d --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/entries.ts @@ -0,0 +1,3 @@ +export function entries(object: T): [keyof T, T[keyof T]][] { + return Object.entries(object) as [keyof T, T[keyof T]][]; +} diff --git a/seed/ts-express/path-parameters/core/schemas/utils/filterObject.ts b/seed/ts-express/path-parameters/core/schemas/utils/filterObject.ts new file mode 100644 index 00000000000..2c25a3455bc --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/filterObject.ts @@ -0,0 +1,10 @@ +export function filterObject(obj: T, keysToInclude: K[]): Pick { + const keysToIncludeSet = new Set(keysToInclude); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (keysToIncludeSet.has(key as K)) { + acc[key as K] = value; + } + return acc; + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + }, {} as Pick); +} diff --git a/seed/ts-express/path-parameters/core/schemas/utils/getErrorMessageForIncorrectType.ts b/seed/ts-express/path-parameters/core/schemas/utils/getErrorMessageForIncorrectType.ts new file mode 100644 index 00000000000..1a5c31027ce --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/getErrorMessageForIncorrectType.ts @@ -0,0 +1,25 @@ +export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { + return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; +} + +function getTypeAsString(value: unknown): string { + if (Array.isArray(value)) { + return "list"; + } + if (value === null) { + return "null"; + } + if (value instanceof BigInt) { + return "BigInt"; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "bigint": + case "number": + case "boolean": + case "undefined": + return `${value}`; + } + return typeof value; +} diff --git a/seed/ts-express/path-parameters/core/schemas/utils/isPlainObject.ts b/seed/ts-express/path-parameters/core/schemas/utils/isPlainObject.ts new file mode 100644 index 00000000000..db82a722c35 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/isPlainObject.ts @@ -0,0 +1,17 @@ +// borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js +export function isPlainObject(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + if (Object.getPrototypeOf(value) === null) { + return true; + } + + let proto = value; + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + + return Object.getPrototypeOf(value) === proto; +} diff --git a/seed/ts-express/path-parameters/core/schemas/utils/keys.ts b/seed/ts-express/path-parameters/core/schemas/utils/keys.ts new file mode 100644 index 00000000000..01867098287 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/keys.ts @@ -0,0 +1,3 @@ +export function keys(object: T): (keyof T)[] { + return Object.keys(object) as (keyof T)[]; +} diff --git a/seed/ts-express/path-parameters/core/schemas/utils/maybeSkipValidation.ts b/seed/ts-express/path-parameters/core/schemas/utils/maybeSkipValidation.ts new file mode 100644 index 00000000000..86c07abf2b4 --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/maybeSkipValidation.ts @@ -0,0 +1,38 @@ +import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; + +export function maybeSkipValidation, Raw, Parsed>(schema: S): S { + return { + ...schema, + json: transformAndMaybeSkipValidation(schema.json), + parse: transformAndMaybeSkipValidation(schema.parse), + }; +} + +function transformAndMaybeSkipValidation( + transform: (value: unknown, opts?: SchemaOptions) => MaybeValid +): (value: unknown, opts?: SchemaOptions) => MaybeValid { + return (value, opts): MaybeValid => { + const transformed = transform(value, opts); + const { skipValidation = false } = opts ?? {}; + if (!transformed.ok && skipValidation) { + // eslint-disable-next-line no-console + console.warn( + [ + "Failed to validate.", + ...transformed.errors.map( + (error) => + " - " + + (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) + ), + ].join("\n") + ); + + return { + ok: true, + value: value as T, + }; + } else { + return transformed; + } + }; +} diff --git a/seed/ts-express/path-parameters/core/schemas/utils/partition.ts b/seed/ts-express/path-parameters/core/schemas/utils/partition.ts new file mode 100644 index 00000000000..f58d6f3d35f --- /dev/null +++ b/seed/ts-express/path-parameters/core/schemas/utils/partition.ts @@ -0,0 +1,12 @@ +export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { + const trueItems: T[] = [], + falseItems: T[] = []; + for (const item of items) { + if (predicate(item)) { + trueItems.push(item); + } else { + falseItems.push(item); + } + } + return [trueItems, falseItems]; +} diff --git a/seed/ts-express/path-parameters/errors/SeedPathParametersError.ts b/seed/ts-express/path-parameters/errors/SeedPathParametersError.ts new file mode 100644 index 00000000000..1bcff595c8e --- /dev/null +++ b/seed/ts-express/path-parameters/errors/SeedPathParametersError.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import express from "express"; + +export abstract class SeedPathParametersError extends Error { + constructor(public readonly errorName?: string) { + super(); + Object.setPrototypeOf(this, SeedPathParametersError.prototype); + } + + public abstract send(res: express.Response): Promise; +} diff --git a/seed/ts-express/path-parameters/errors/index.ts b/seed/ts-express/path-parameters/errors/index.ts new file mode 100644 index 00000000000..ad127bdd146 --- /dev/null +++ b/seed/ts-express/path-parameters/errors/index.ts @@ -0,0 +1 @@ +export { SeedPathParametersError } from "./SeedPathParametersError"; diff --git a/seed/ts-express/path-parameters/index.ts b/seed/ts-express/path-parameters/index.ts new file mode 100644 index 00000000000..f171f9b987b --- /dev/null +++ b/seed/ts-express/path-parameters/index.ts @@ -0,0 +1,3 @@ +export * as SeedPathParameters from "./api"; +export { register } from "./register"; +export { SeedPathParametersError } from "./errors"; diff --git a/seed/ts-express/path-parameters/register.ts b/seed/ts-express/path-parameters/register.ts new file mode 100644 index 00000000000..cceae2c734a --- /dev/null +++ b/seed/ts-express/path-parameters/register.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import express from "express"; +import { UserService } from "./api/resources/user/service/UserService"; + +export function register( + expressApp: express.Express | express.Router, + services: { + user: UserService; + } +): void { + (expressApp as any).use("/user", services.user.toRouter()); +} diff --git a/seed/ts-express/path-parameters/serialization/index.ts b/seed/ts-express/path-parameters/serialization/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-express/path-parameters/serialization/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-express/path-parameters/serialization/resources/index.ts b/seed/ts-express/path-parameters/serialization/resources/index.ts new file mode 100644 index 00000000000..e2e6c212efb --- /dev/null +++ b/seed/ts-express/path-parameters/serialization/resources/index.ts @@ -0,0 +1,2 @@ +export * as user from "./user"; +export * from "./user/types"; diff --git a/seed/ts-express/path-parameters/serialization/resources/user/index.ts b/seed/ts-express/path-parameters/serialization/resources/user/index.ts new file mode 100644 index 00000000000..eea524d6557 --- /dev/null +++ b/seed/ts-express/path-parameters/serialization/resources/user/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/seed/ts-express/path-parameters/serialization/resources/user/types/User.ts b/seed/ts-express/path-parameters/serialization/resources/user/types/User.ts new file mode 100644 index 00000000000..f85255d90a7 --- /dev/null +++ b/seed/ts-express/path-parameters/serialization/resources/user/types/User.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedPathParameters from "../../../../api/index"; +import * as core from "../../../../core"; + +export const User: core.serialization.ObjectSchema = + core.serialization.object({ + name: core.serialization.string(), + tags: core.serialization.list(core.serialization.string()), + }); + +export declare namespace User { + interface Raw { + name: string; + tags: string[]; + } +} diff --git a/seed/ts-express/path-parameters/serialization/resources/user/types/index.ts b/seed/ts-express/path-parameters/serialization/resources/user/types/index.ts new file mode 100644 index 00000000000..3ce758c1197 --- /dev/null +++ b/seed/ts-express/path-parameters/serialization/resources/user/types/index.ts @@ -0,0 +1 @@ +export * from "./User"; diff --git a/seed/ts-express/path-parameters/snippet-templates.json b/seed/ts-express/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ts-express/path-parameters/snippet.json b/seed/ts-express/path-parameters/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ts-sdk/path-parameters/.github/workflows/ci.yml b/seed/ts-sdk/path-parameters/.github/workflows/ci.yml new file mode 100644 index 00000000000..b64a6cbbb4a --- /dev/null +++ b/seed/ts-sdk/path-parameters/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up node + uses: actions/setup-node@v3 + + - name: Compile + run: yarn && yarn build + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up node + uses: actions/setup-node@v3 + + - name: Compile + run: yarn && yarn test + + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up node + uses: actions/setup-node@v3 + - name: Install dependencies + run: yarn install + - name: Build + run: yarn build + + - name: Publish to npm + run: | + npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} + if [[ ${GITHUB_REF} == *alpha* ]]; then + npm publish --access public --tag alpha + elif [[ ${GITHUB_REF} == *beta* ]]; then + npm publish --access public --tag beta + else + npm publish --access public + fi + env: + NPM_TOKEN: ${{ secrets. }} \ No newline at end of file diff --git a/seed/ts-sdk/path-parameters/.gitignore b/seed/ts-sdk/path-parameters/.gitignore new file mode 100644 index 00000000000..72271e049c0 --- /dev/null +++ b/seed/ts-sdk/path-parameters/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store +/dist \ No newline at end of file diff --git a/seed/ts-sdk/path-parameters/.mock/definition/api.yml b/seed/ts-sdk/path-parameters/.mock/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/seed/ts-sdk/path-parameters/.mock/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/seed/ts-sdk/path-parameters/.mock/definition/user.yml b/seed/ts-sdk/path-parameters/.mock/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/seed/ts-sdk/path-parameters/.mock/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/seed/ts-sdk/path-parameters/.mock/fern.config.json b/seed/ts-sdk/path-parameters/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ts-sdk/path-parameters/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ts-sdk/path-parameters/.mock/generators.yml b/seed/ts-sdk/path-parameters/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/ts-sdk/path-parameters/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/ts-sdk/path-parameters/.npmignore b/seed/ts-sdk/path-parameters/.npmignore new file mode 100644 index 00000000000..6db0876c41c --- /dev/null +++ b/seed/ts-sdk/path-parameters/.npmignore @@ -0,0 +1,9 @@ +node_modules +src +tests +.gitignore +.github +.fernignore +.prettierrc.yml +tsconfig.json +yarn.lock \ No newline at end of file diff --git a/seed/ts-sdk/path-parameters/.prettierrc.yml b/seed/ts-sdk/path-parameters/.prettierrc.yml new file mode 100644 index 00000000000..0c06786bf53 --- /dev/null +++ b/seed/ts-sdk/path-parameters/.prettierrc.yml @@ -0,0 +1,2 @@ +tabWidth: 4 +printWidth: 120 diff --git a/seed/ts-sdk/path-parameters/README.md b/seed/ts-sdk/path-parameters/README.md new file mode 100644 index 00000000000..a986c62d502 --- /dev/null +++ b/seed/ts-sdk/path-parameters/README.md @@ -0,0 +1,163 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) +[![npm shield](https://img.shields.io/npm/v/@fern/path-parameters)](https://www.npmjs.com/package/@fern/path-parameters) + +The Seed TypeScript library provides convenient access to the Seed API from TypeScript. + +## Installation + +```sh +npm i -s @fern/path-parameters +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedPathParametersClient, SeedPathParameters } from "@fern/path-parameters"; + +const client = new SeedPathParametersClient({ environment: "YOUR_BASE_URL" }); +await client.user.getOrganization("organizationId"); +``` + +## Request And Response Types + +The SDK exports all request and response types as TypeScript interfaces. Simply import them with the +following namespace: + +```typescript +import { SeedPathParameters } from "@fern/path-parameters"; + +const request: SeedPathParameters.GetUsersRequest = { + ... +}; +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedPathParametersError } from "@fern/path-parameters"; + +try { + await client.user.getOrganization(...); +} catch (err) { + if (err instanceof SeedPathParametersError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + } +} +``` + +## Advanced + +### Raw Responses + +The SDK provides access to raw response data, including headers, through the `.asRaw()` method. When using `.asRaw()`, +the parsed response body will be available in the `body` field, along with the response headers: + +```typescript +const response = await client.user.getOrganization(...).asRaw(); + +console.log(response.headers['X-My-Header']); +console.log(response.body); +``` + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `headers` request option. + +```typescript +const response = await client.user.getOrganization(..., { + headers: { + 'X-Custom-Header': 'custom value' + } +}); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.user.getOrganization(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.user.getOrganization(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.user.getOrganization(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Runtime Compatibility + +The SDK defaults to `node-fetch` but will use the global fetch client if present. The SDK works in the following +runtimes: + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + +### Customizing Fetch Client + +The SDK provides a way for your to customize the underlying HTTP client / Fetch function. If you're running in an +unsupported environment, this provides a way for you to break glass and ensure the SDK works. + +```typescript +import { SeedPathParametersClient } from "@fern/path-parameters"; + +const client = new SeedPathParametersClient({ + ... + fetcher: // provide your implementation here +}); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/path-parameters/jest.config.js b/seed/ts-sdk/path-parameters/jest.config.js new file mode 100644 index 00000000000..35d6e65bf93 --- /dev/null +++ b/seed/ts-sdk/path-parameters/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('jest').Config} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", +}; diff --git a/seed/ts-sdk/path-parameters/package.json b/seed/ts-sdk/path-parameters/package.json new file mode 100644 index 00000000000..a59e99f2ee3 --- /dev/null +++ b/seed/ts-sdk/path-parameters/package.json @@ -0,0 +1,42 @@ +{ + "name": "@fern/path-parameters", + "version": "0.0.1", + "private": false, + "repository": "https://github.com/path-parameters/fern", + "main": "./index.js", + "types": "./index.d.ts", + "scripts": { + "format": "prettier . --write --ignore-unknown", + "build": "tsc", + "prepack": "cp -rv dist/. .", + "test": "jest" + }, + "dependencies": { + "url-join": "4.0.1", + "form-data": "^4.0.0", + "formdata-node": "^6.0.3", + "node-fetch": "2.7.0", + "qs": "6.11.2", + "readable-stream": "^4.5.2" + }, + "devDependencies": { + "@types/url-join": "4.0.1", + "@types/qs": "6.9.8", + "@types/node-fetch": "2.6.9", + "@types/readable-stream": "^4.0.15", + "webpack": "^5.94.0", + "ts-loader": "^9.3.1", + "jest": "29.7.0", + "@types/jest": "29.5.5", + "ts-jest": "29.1.1", + "jest-environment-jsdom": "29.7.0", + "@types/node": "17.0.33", + "prettier": "2.7.1", + "typescript": "4.6.4" + }, + "browser": { + "fs": false, + "os": false, + "path": false + } +} diff --git a/seed/ts-sdk/path-parameters/reference.md b/seed/ts-sdk/path-parameters/reference.md new file mode 100644 index 00000000000..7d5a1e6fa82 --- /dev/null +++ b/seed/ts-sdk/path-parameters/reference.md @@ -0,0 +1,171 @@ +# Reference + +## User + +

client.user.getOrganization(organizationId) -> SeedPathParameters.User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.getOrganization("organizationId"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**organizationId:** `string` + +
+
+ +
+
+ +**requestOptions:** `User.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.user.getUser(userId, { ...params }) -> SeedPathParameters.User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.getUser("userId"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**userId:** `string` + +
+
+ +
+
+ +**request:** `SeedPathParameters.GetUsersRequest` + +
+
+ +
+
+ +**requestOptions:** `User.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.user.getOrganizationUser(organizationId, userId, { ...params }) -> SeedPathParameters.User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.getOrganizationUser("organizationId", "userId"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**organizationId:** `string` + +
+
+ +
+
+ +**userId:** `string` + +
+
+ +
+
+ +**request:** `SeedPathParameters.GetOrganizationUserRequest` + +
+
+ +
+
+ +**requestOptions:** `User.RequestOptions` + +
+
+
+
+ +
+
+
diff --git a/seed/ts-sdk/path-parameters/resolved-snippet-templates.md b/seed/ts-sdk/path-parameters/resolved-snippet-templates.md new file mode 100644 index 00000000000..5e1f3ee8769 --- /dev/null +++ b/seed/ts-sdk/path-parameters/resolved-snippet-templates.md @@ -0,0 +1,27 @@ +```typescript +import { SeedPathParametersClient } from "@fern/path-parameters"; + +const client = new SeedPathParametersClient({ environment: "YOUR_BASE_URL" }); +await client.user.getOrganization("organizationId"); + +``` + + +```typescript +import { SeedPathParametersClient } from "@fern/path-parameters"; + +const client = new SeedPathParametersClient({ environment: "YOUR_BASE_URL" }); +await client.user.getUser("userId"); + +``` + + +```typescript +import { SeedPathParametersClient } from "@fern/path-parameters"; + +const client = new SeedPathParametersClient({ environment: "YOUR_BASE_URL" }); +await client.user.getOrganizationUser("organizationId", "userId"); + +``` + + diff --git a/seed/ts-sdk/path-parameters/snippet-templates.json b/seed/ts-sdk/path-parameters/snippet-templates.json new file mode 100644 index 00000000000..ac0b9d882c3 --- /dev/null +++ b/seed/ts-sdk/path-parameters/snippet-templates.json @@ -0,0 +1,270 @@ +[ + { + "sdk": { + "package": "@fern/path-parameters", + "version": "0.0.1", + "type": "typescript" + }, + "endpointId": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganization" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "import { SeedPathParametersClient } from \"@fern/path-parameters\";" + ], + "templateString": "const client = new SeedPathParametersClient($FERN_INPUT);", + "isOptional": false, + "inputDelimiter": ",", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{ $FERN_INPUT }", + "isOptional": true, + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "environment: \"YOUR_BASE_URL\"", + "isOptional": false, + "templateInputs": [], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "templateString": "await client.user.getOrganization(\n\t$FERN_INPUT\n)", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "PATH", + "path": "organizationId", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "v1" + } + }, + { + "sdk": { + "package": "@fern/path-parameters", + "version": "0.0.1", + "type": "typescript" + }, + "endpointId": { + "path": "/user/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "import { SeedPathParametersClient } from \"@fern/path-parameters\";" + ], + "templateString": "const client = new SeedPathParametersClient($FERN_INPUT);", + "isOptional": false, + "inputDelimiter": ",", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{ $FERN_INPUT }", + "isOptional": true, + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "environment: \"YOUR_BASE_URL\"", + "isOptional": false, + "templateInputs": [], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "templateString": "await client.user.getUser(\n\t$FERN_INPUT\n)", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "PATH", + "path": "userId", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "v1" + } + }, + { + "sdk": { + "package": "@fern/path-parameters", + "version": "0.0.1", + "type": "typescript" + }, + "endpointId": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifierOverride": "endpoint_user.getOrganizationUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "import { SeedPathParametersClient } from \"@fern/path-parameters\";" + ], + "templateString": "const client = new SeedPathParametersClient($FERN_INPUT);", + "isOptional": false, + "inputDelimiter": ",", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{ $FERN_INPUT }", + "isOptional": true, + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "environment: \"YOUR_BASE_URL\"", + "isOptional": false, + "templateInputs": [], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "templateString": "await client.user.getOrganizationUser(\n\t$FERN_INPUT\n)", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "PATH", + "path": "organizationId", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "PATH", + "path": "userId", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "v1" + } + } +] \ No newline at end of file diff --git a/seed/ts-sdk/path-parameters/snippet.json b/seed/ts-sdk/path-parameters/snippet.json new file mode 100644 index 00000000000..b8756505a79 --- /dev/null +++ b/seed/ts-sdk/path-parameters/snippet.json @@ -0,0 +1,38 @@ +{ + "endpoints": [ + { + "id": { + "path": "/user/organizations/{organizationId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganization" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedPathParametersClient, SeedPathParameters } from \"@fern/path-parameters\";\n\nconst client = new SeedPathParametersClient({ environment: \"YOUR_BASE_URL\" });\nawait client.user.getOrganization(\"organizationId\");\n" + } + }, + { + "id": { + "path": "/user/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getUser" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedPathParametersClient, SeedPathParameters } from \"@fern/path-parameters\";\n\nconst client = new SeedPathParametersClient({ environment: \"YOUR_BASE_URL\" });\nawait client.user.getUser(\"userId\");\n" + } + }, + { + "id": { + "path": "/user/organizations/{organizationId}/users/{userId}", + "method": "GET", + "identifier_override": "endpoint_user.getOrganizationUser" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedPathParametersClient, SeedPathParameters } from \"@fern/path-parameters\";\n\nconst client = new SeedPathParametersClient({ environment: \"YOUR_BASE_URL\" });\nawait client.user.getOrganizationUser(\"organizationId\", \"userId\");\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/ts-sdk/path-parameters/src/Client.ts b/seed/ts-sdk/path-parameters/src/Client.ts new file mode 100644 index 00000000000..4d69aeee0a4 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/Client.ts @@ -0,0 +1,33 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "./core"; +import { User } from "./api/resources/user/client/Client"; + +export declare namespace SeedPathParametersClient { + interface Options { + environment: core.Supplier; + } + + interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional headers to include in the request. */ + headers?: Record; + } +} + +export class SeedPathParametersClient { + constructor(protected readonly _options: SeedPathParametersClient.Options) {} + + protected _user: User | undefined; + + public get user(): User { + return (this._user ??= new User(this._options)); + } +} diff --git a/seed/ts-sdk/path-parameters/src/api/index.ts b/seed/ts-sdk/path-parameters/src/api/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-sdk/path-parameters/src/api/resources/index.ts b/seed/ts-sdk/path-parameters/src/api/resources/index.ts new file mode 100644 index 00000000000..0671bd81e84 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/index.ts @@ -0,0 +1,3 @@ +export * as user from "./user"; +export * from "./user/types"; +export * from "./user/client/requests"; diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/client/Client.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/client/Client.ts new file mode 100644 index 00000000000..ef226eb0877 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/client/Client.ts @@ -0,0 +1,246 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core"; +import * as SeedPathParameters from "../../../index"; +import urlJoin from "url-join"; +import * as serializers from "../../../../serialization/index"; +import * as errors from "../../../../errors/index"; + +export declare namespace User { + interface Options { + environment: core.Supplier; + } + + interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + /** Additional headers to include in the request. */ + headers?: Record; + } +} + +export class User { + constructor(protected readonly _options: User.Options) {} + + /** + * @param {string} organizationId + * @param {User.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.user.getOrganization("organizationId") + */ + public getOrganization( + organizationId: string, + requestOptions?: User.RequestOptions + ): core.APIPromise { + return core.APIPromise.from( + (async () => { + const _response = await core.fetcher({ + url: urlJoin( + await core.Supplier.get(this._options.environment), + `/user/organizations/${encodeURIComponent(organizationId)}` + ), + method: "GET", + headers: { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/path-parameters", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/path-parameters/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + ...requestOptions?.headers, + }, + contentType: "application/json", + requestType: "json", + timeoutMs: + requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { + ok: _response.ok, + body: serializers.User.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + breadcrumbsPrefix: ["response"], + }), + headers: _response.headers, + }; + } + if (_response.error.reason === "status-code") { + throw new errors.SeedPathParametersError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + }); + } + switch (_response.error.reason) { + case "non-json": + throw new errors.SeedPathParametersError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + }); + case "timeout": + throw new errors.SeedPathParametersTimeoutError(); + case "unknown": + throw new errors.SeedPathParametersError({ + message: _response.error.errorMessage, + }); + } + })() + ); + } + + /** + * @param {string} userId + * @param {SeedPathParameters.GetUsersRequest} request + * @param {User.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.user.getUser("userId") + */ + public getUser( + userId: string, + request: SeedPathParameters.GetUsersRequest = {}, + requestOptions?: User.RequestOptions + ): core.APIPromise { + return core.APIPromise.from( + (async () => { + const _response = await core.fetcher({ + url: urlJoin( + await core.Supplier.get(this._options.environment), + `/user/users/${encodeURIComponent(userId)}` + ), + method: "GET", + headers: { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/path-parameters", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/path-parameters/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + ...requestOptions?.headers, + }, + contentType: "application/json", + requestType: "json", + timeoutMs: + requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { + ok: _response.ok, + body: serializers.User.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + breadcrumbsPrefix: ["response"], + }), + headers: _response.headers, + }; + } + if (_response.error.reason === "status-code") { + throw new errors.SeedPathParametersError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + }); + } + switch (_response.error.reason) { + case "non-json": + throw new errors.SeedPathParametersError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + }); + case "timeout": + throw new errors.SeedPathParametersTimeoutError(); + case "unknown": + throw new errors.SeedPathParametersError({ + message: _response.error.errorMessage, + }); + } + })() + ); + } + + /** + * @param {string} organizationId + * @param {string} userId + * @param {SeedPathParameters.GetOrganizationUserRequest} request + * @param {User.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.user.getOrganizationUser("organizationId", "userId") + */ + public getOrganizationUser( + organizationId: string, + userId: string, + request: SeedPathParameters.GetOrganizationUserRequest = {}, + requestOptions?: User.RequestOptions + ): core.APIPromise { + return core.APIPromise.from( + (async () => { + const _response = await core.fetcher({ + url: urlJoin( + await core.Supplier.get(this._options.environment), + `/user/organizations/${encodeURIComponent(organizationId)}/users/${encodeURIComponent(userId)}` + ), + method: "GET", + headers: { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/path-parameters", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/path-parameters/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + ...requestOptions?.headers, + }, + contentType: "application/json", + requestType: "json", + timeoutMs: + requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { + ok: _response.ok, + body: serializers.User.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + breadcrumbsPrefix: ["response"], + }), + headers: _response.headers, + }; + } + if (_response.error.reason === "status-code") { + throw new errors.SeedPathParametersError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + }); + } + switch (_response.error.reason) { + case "non-json": + throw new errors.SeedPathParametersError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + }); + case "timeout": + throw new errors.SeedPathParametersTimeoutError(); + case "unknown": + throw new errors.SeedPathParametersError({ + message: _response.error.errorMessage, + }); + } + })() + ); + } +} diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/client/index.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/client/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/client/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/GetOrganizationUserRequest.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/GetOrganizationUserRequest.ts new file mode 100644 index 00000000000..70332b0a087 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/GetOrganizationUserRequest.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * {} + */ +export interface GetOrganizationUserRequest {} diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/GetUsersRequest.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/GetUsersRequest.ts new file mode 100644 index 00000000000..14b8a70eac2 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/GetUsersRequest.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * {} + */ +export interface GetUsersRequest {} diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/index.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/index.ts new file mode 100644 index 00000000000..1858c592a58 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/client/requests/index.ts @@ -0,0 +1,2 @@ +export { type GetUsersRequest } from "./GetUsersRequest"; +export { type GetOrganizationUserRequest } from "./GetOrganizationUserRequest"; diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/index.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/index.ts new file mode 100644 index 00000000000..c9240f83b48 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./client"; diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/types/User.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/types/User.ts new file mode 100644 index 00000000000..4e9bf453214 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/types/User.ts @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface User { + name: string; + tags: string[]; +} diff --git a/seed/ts-sdk/path-parameters/src/api/resources/user/types/index.ts b/seed/ts-sdk/path-parameters/src/api/resources/user/types/index.ts new file mode 100644 index 00000000000..3ce758c1197 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/api/resources/user/types/index.ts @@ -0,0 +1 @@ +export * from "./User"; diff --git a/seed/ts-sdk/path-parameters/src/core/api-promise/APIPromise.ts b/seed/ts-sdk/path-parameters/src/core/api-promise/APIPromise.ts new file mode 100644 index 00000000000..0dadda8a6d7 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/api-promise/APIPromise.ts @@ -0,0 +1,54 @@ +import { APIResponse } from "../fetcher/APIResponse"; + +/** + * APIPromise wraps a Promise that resolves with an APIResponse. + * It provides convenient methods for handling both successful responses and errors. + * + * By default, when awaited, it will return just the response body data. + * Use the `asRaw()` method to get access to both the response data and headers. + * + * @example + * // Get just the response data + * const data = await apiPromise; + * + * // Get response with headers + * const { data, headers } = await apiPromise.asRaw(); + * + * @template T The type of the successful response body + */ +export class APIPromise extends Promise { + constructor( + private readonly responsePromise: Promise>, + executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void) => void + ) { + super(executor); + } + + public async asRaw(): Promise<{ + data: T; + headers?: Record; + }> { + const response = await this.responsePromise; + if (!response.ok) { + throw response.error; + } + return { + data: response.body, + headers: response.headers, + }; + } + + public static from(responsePromise: Promise>): APIPromise { + return new APIPromise(responsePromise, (resolve, reject) => { + responsePromise + .then((response) => { + if (response.ok) { + resolve(response.body); + } else { + reject(response.error); + } + }) + .catch(reject); + }); + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/api-promise/index.ts b/seed/ts-sdk/path-parameters/src/core/api-promise/index.ts new file mode 100644 index 00000000000..91b2cf080cd --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/api-promise/index.ts @@ -0,0 +1 @@ +export { APIPromise } from "./APIPromise"; diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/APIResponse.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/APIResponse.ts new file mode 100644 index 00000000000..3664d09e168 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/APIResponse.ts @@ -0,0 +1,12 @@ +export type APIResponse = SuccessfulResponse | FailedResponse; + +export interface SuccessfulResponse { + ok: true; + body: T; + headers?: Record; +} + +export interface FailedResponse { + ok: false; + error: T; +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/Fetcher.ts new file mode 100644 index 00000000000..b8f23717b69 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/Fetcher.ts @@ -0,0 +1,143 @@ +import { APIResponse } from "./APIResponse"; +import { createRequestUrl } from "./createRequestUrl"; +import { getFetchFn } from "./getFetchFn"; +import { getRequestBody } from "./getRequestBody"; +import { getResponseBody } from "./getResponseBody"; +import { makeRequest } from "./makeRequest"; +import { requestWithRetries } from "./requestWithRetries"; + +export type FetchFunction = (args: Fetcher.Args) => Promise>; + +export declare namespace Fetcher { + export interface Args { + url: string; + method: string; + contentType?: string; + headers?: Record; + queryParameters?: Record; + body?: unknown; + timeoutMs?: number; + maxRetries?: number; + withCredentials?: boolean; + abortSignal?: AbortSignal; + requestType?: "json" | "file" | "bytes"; + responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer"; + duplex?: "half"; + } + + export type Error = FailedStatusCodeError | NonJsonError | TimeoutError | UnknownError; + + export interface FailedStatusCodeError { + reason: "status-code"; + statusCode: number; + body: unknown; + } + + export interface NonJsonError { + reason: "non-json"; + statusCode: number; + rawBody: string; + } + + export interface TimeoutError { + reason: "timeout"; + } + + export interface UnknownError { + reason: "unknown"; + errorMessage: string; + } +} + +export async function fetcherImpl(args: Fetcher.Args): Promise> { + const headers: Record = {}; + if (args.body !== undefined && args.contentType != null) { + headers["Content-Type"] = args.contentType; + } + + if (args.headers != null) { + for (const [key, value] of Object.entries(args.headers)) { + if (value != null) { + headers[key] = value; + } + } + } + + const url = createRequestUrl(args.url, args.queryParameters); + let requestBody: BodyInit | undefined = await getRequestBody({ + body: args.body, + type: args.requestType === "json" ? "json" : "other", + }); + const fetchFn = await getFetchFn(); + + try { + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + url, + args.method, + headers, + requestBody, + args.timeoutMs, + args.abortSignal, + args.withCredentials, + args.duplex + ), + args.maxRetries + ); + let responseBody = await getResponseBody(response, args.responseType); + + if (response.status >= 200 && response.status < 400) { + return { + ok: true, + body: responseBody as R, + headers: response.headers, + }; + } else { + return { + ok: false, + error: { + reason: "status-code", + statusCode: response.status, + body: responseBody, + }, + }; + } + } catch (error) { + if (args.abortSignal != null && args.abortSignal.aborted) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: "The user aborted a request", + }, + }; + } else if (error instanceof Error && error.name === "AbortError") { + return { + ok: false, + error: { + reason: "timeout", + }, + }; + } else if (error instanceof Error) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: error.message, + }, + }; + } + + return { + ok: false, + error: { + reason: "unknown", + errorMessage: JSON.stringify(error), + }, + }; + } +} + +export const fetcher: FetchFunction = fetcherImpl; diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/Supplier.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/Supplier.ts new file mode 100644 index 00000000000..867c931c02f --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/Supplier.ts @@ -0,0 +1,11 @@ +export type Supplier = T | Promise | (() => T | Promise); + +export const Supplier = { + get: async (supplier: Supplier): Promise => { + if (typeof supplier === "function") { + return (supplier as () => T)(); + } else { + return supplier; + } + }, +}; diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/createRequestUrl.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/createRequestUrl.ts new file mode 100644 index 00000000000..9288a99bb22 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/createRequestUrl.ts @@ -0,0 +1,10 @@ +import qs from "qs"; + +export function createRequestUrl( + baseUrl: string, + queryParameters?: Record +): string { + return Object.keys(queryParameters ?? {}).length > 0 + ? `${baseUrl}?${qs.stringify(queryParameters, { arrayFormat: "repeat" })}` + : baseUrl; +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/getFetchFn.ts new file mode 100644 index 00000000000..9fd9bfc42bd --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/getFetchFn.ts @@ -0,0 +1,25 @@ +import { RUNTIME } from "../runtime"; + +/** + * Returns a fetch function based on the runtime + */ +export async function getFetchFn(): Promise { + // In Node.js 18+ environments, use native fetch + if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + return fetch; + } + + // In Node.js 18 or lower environments, the SDK always uses`node-fetch`. + if (RUNTIME.type === "node") { + return (await import("node-fetch")).default as any; + } + + // Otherwise the SDK uses global fetch if available, + // and falls back to node-fetch. + if (typeof fetch == "function") { + return fetch; + } + + // Defaults to node `node-fetch` if global fetch isn't available + return (await import("node-fetch")).default as any; +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/getHeader.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/getHeader.ts new file mode 100644 index 00000000000..50f922b0e87 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/getHeader.ts @@ -0,0 +1,8 @@ +export function getHeader(headers: Record, header: string): string | undefined { + for (const [headerKey, headerValue] of Object.entries(headers)) { + if (headerKey.toLowerCase() === header.toLowerCase()) { + return headerValue; + } + } + return undefined; +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/getRequestBody.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/getRequestBody.ts new file mode 100644 index 00000000000..1138414b1c2 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/getRequestBody.ts @@ -0,0 +1,14 @@ +export declare namespace GetRequestBody { + interface Args { + body: unknown; + type: "json" | "file" | "bytes" | "other"; + } +} + +export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { + if (type.includes("json")) { + return JSON.stringify(body); + } else { + return body as BodyInit; + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/getResponseBody.ts new file mode 100644 index 00000000000..d046e6ea275 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/getResponseBody.ts @@ -0,0 +1,34 @@ +import { chooseStreamWrapper } from "./stream-wrappers/chooseStreamWrapper"; + +export async function getResponseBody(response: Response, responseType?: string): Promise { + if (response.body != null && responseType === "blob") { + return await response.blob(); + } else if (response.body != null && responseType === "arrayBuffer") { + return await response.arrayBuffer(); + } else if (response.body != null && responseType === "sse") { + return response.body; + } else if (response.body != null && responseType === "streaming") { + return chooseStreamWrapper(response.body); + } else if (response.body != null && responseType === "text") { + return await response.text(); + } else { + const text = await response.text(); + if (text.length > 0) { + try { + let responseBody = JSON.parse(text); + return responseBody; + } catch (err) { + return { + ok: false, + error: { + reason: "non-json", + statusCode: response.status, + rawBody: text, + }, + }; + } + } else { + return undefined; + } + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/index.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/index.ts new file mode 100644 index 00000000000..2d658ca48f9 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/index.ts @@ -0,0 +1,5 @@ +export type { APIResponse } from "./APIResponse"; +export { fetcher } from "./Fetcher"; +export type { Fetcher, FetchFunction } from "./Fetcher"; +export { getHeader } from "./getHeader"; +export { Supplier } from "./Supplier"; diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/makeRequest.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/makeRequest.ts new file mode 100644 index 00000000000..8fb4bace466 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/makeRequest.ts @@ -0,0 +1,44 @@ +import { anySignal, getTimeoutSignal } from "./signals"; + +export const makeRequest = async ( + fetchFn: (url: string, init: RequestInit) => Promise, + url: string, + method: string, + headers: Record, + requestBody: BodyInit | undefined, + timeoutMs?: number, + abortSignal?: AbortSignal, + withCredentials?: boolean, + duplex?: "half" +): Promise => { + const signals: AbortSignal[] = []; + + // Add timeout signal + let timeoutAbortId: NodeJS.Timeout | undefined = undefined; + if (timeoutMs != null) { + const { signal, abortId } = getTimeoutSignal(timeoutMs); + timeoutAbortId = abortId; + signals.push(signal); + } + + // Add arbitrary signal + if (abortSignal != null) { + signals.push(abortSignal); + } + let newSignals = anySignal(signals); + const response = await fetchFn(url, { + method: method, + headers, + body: requestBody, + signal: newSignals, + credentials: withCredentials ? "include" : undefined, + // @ts-ignore + duplex, + }); + + if (timeoutAbortId != null) { + clearTimeout(timeoutAbortId); + } + + return response; +}; diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/requestWithRetries.ts new file mode 100644 index 00000000000..8d5af9d5a82 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/requestWithRetries.ts @@ -0,0 +1,33 @@ +const INITIAL_RETRY_DELAY = 1000; // in milliseconds +const MAX_RETRY_DELAY = 60000; // in milliseconds +const DEFAULT_MAX_RETRIES = 2; +const JITTER_FACTOR = 0.2; // 20% random jitter + +function addJitter(delay: number): number { + // Generate a random value between -JITTER_FACTOR and +JITTER_FACTOR + const jitterMultiplier = 1 + (Math.random() * 2 - 1) * JITTER_FACTOR; + return delay * jitterMultiplier; +} + +export async function requestWithRetries( + requestFn: () => Promise, + maxRetries: number = DEFAULT_MAX_RETRIES +): Promise { + let response: Response = await requestFn(); + + for (let i = 0; i < maxRetries; ++i) { + if ([408, 409, 429].includes(response.status) || response.status >= 500) { + // Calculate base delay using exponential backoff (in milliseconds) + const baseDelay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, i), MAX_RETRY_DELAY); + + // Add jitter to the delay + const delayWithJitter = addJitter(baseDelay); + + await new Promise((resolve) => setTimeout(resolve, delayWithJitter)); + response = await requestFn(); + } else { + break; + } + } + return response!; +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/signals.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/signals.ts new file mode 100644 index 00000000000..6c124ff7985 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/signals.ts @@ -0,0 +1,38 @@ +const TIMEOUT = "timeout"; + +export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: NodeJS.Timeout } { + const controller = new AbortController(); + const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); + return { signal: controller.signal, abortId }; +} + +/** + * Returns an abort signal that is getting aborted when + * at least one of the specified abort signals is aborted. + * + * Requires at least node.js 18. + */ +export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { + // Allowing signals to be passed either as array + // of signals or as multiple arguments. + const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args); + + const controller = new AbortController(); + + for (const signal of signals) { + if (signal.aborted) { + // Exiting early if one of the signals + // is already aborted. + controller.abort((signal as any)?.reason); + break; + } + + // Listening for signals and removing the listeners + // when at least one symbol is aborted. + signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { + signal: controller.signal, + }); + } + + return controller.signal; +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts new file mode 100644 index 00000000000..4d7b7d52e8f --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts @@ -0,0 +1,256 @@ +import type { Writable } from "readable-stream"; +import { EventCallback, StreamWrapper } from "./chooseStreamWrapper"; + +export class Node18UniversalStreamWrapper + implements + StreamWrapper | Writable | WritableStream, ReadFormat> +{ + private readableStream: ReadableStream; + private reader: ReadableStreamDefaultReader; + private events: Record; + private paused: boolean; + private resumeCallback: ((value?: unknown) => void) | null; + private encoding: string | null; + + constructor(readableStream: ReadableStream) { + this.readableStream = readableStream; + this.reader = this.readableStream.getReader(); + this.events = { + data: [], + end: [], + error: [], + readable: [], + close: [], + pause: [], + resume: [], + }; + this.paused = false; + this.resumeCallback = null; + this.encoding = null; + } + + public on(event: string, callback: EventCallback): void { + this.events[event]?.push(callback); + } + + public off(event: string, callback: EventCallback): void { + this.events[event] = this.events[event]?.filter((cb) => cb !== callback); + } + + public pipe( + dest: Node18UniversalStreamWrapper | Writable | WritableStream + ): Node18UniversalStreamWrapper | Writable | WritableStream { + this.on("data", async (chunk) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._write(chunk); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } else { + dest.write(chunk); + } + }); + + this.on("end", async () => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._end(); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.close(); + } else { + dest.end(); + } + }); + + this.on("error", async (error) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._error(error); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.abort(error); + } else { + dest.destroy(error); + } + }); + + this._startReading(); + + return dest; + } + + public pipeTo( + dest: Node18UniversalStreamWrapper | Writable | WritableStream + ): Node18UniversalStreamWrapper | Writable | WritableStream { + return this.pipe(dest); + } + + public unpipe(dest: Node18UniversalStreamWrapper | Writable | WritableStream): void { + this.off("data", async (chunk) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._write(chunk); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } else { + dest.write(chunk); + } + }); + + this.off("end", async () => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._end(); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.close(); + } else { + dest.end(); + } + }); + + this.off("error", async (error) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._error(error); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.abort(error); + } else { + dest.destroy(error); + } + }); + } + + public destroy(error?: Error): void { + this.reader + .cancel(error) + .then(() => { + this._emit("close"); + }) + .catch((err) => { + this._emit("error", err); + }); + } + + public pause(): void { + this.paused = true; + this._emit("pause"); + } + + public resume(): void { + if (this.paused) { + this.paused = false; + this._emit("resume"); + if (this.resumeCallback) { + this.resumeCallback(); + this.resumeCallback = null; + } + } + } + + public get isPaused(): boolean { + return this.paused; + } + + public async read(): Promise { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + + if (done) { + return undefined; + } + return value; + } + + public setEncoding(encoding: string): void { + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: ReadFormat[] = []; + + while (true) { + const { done, value } = await this.reader.read(); + if (done) { + break; + } + if (value) { + chunks.push(value); + } + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(await new Blob(chunks).arrayBuffer()); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + private _write(chunk: ReadFormat): void { + this._emit("data", chunk); + } + + private _end(): void { + this._emit("end"); + } + + private _error(error: any): void { + this._emit("error", error); + } + + private _emit(event: string, data?: any): void { + if (this.events[event]) { + for (const callback of this.events[event] || []) { + callback(data); + } + } + } + + private async _startReading(): Promise { + try { + this._emit("readable"); + while (true) { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + this._emit("end"); + this._emit("close"); + break; + } + if (value) { + this._emit("data", value); + } + } + } catch (error) { + this._emit("error", error); + } + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + return { + next: async () => { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return { done: true, value: undefined }; + } + return { done: false, value }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts new file mode 100644 index 00000000000..ba5f7276750 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts @@ -0,0 +1,106 @@ +import type { Readable, Writable } from "readable-stream"; +import { EventCallback, StreamWrapper } from "./chooseStreamWrapper"; + +export class NodePre18StreamWrapper implements StreamWrapper { + private readableStream: Readable; + private encoding: string | undefined; + + constructor(readableStream: Readable) { + this.readableStream = readableStream; + } + + public on(event: string, callback: EventCallback): void { + this.readableStream.on(event, callback); + } + + public off(event: string, callback: EventCallback): void { + this.readableStream.off(event, callback); + } + + public pipe(dest: Writable): Writable { + this.readableStream.pipe(dest); + return dest; + } + + public pipeTo(dest: Writable): Writable { + return this.pipe(dest); + } + + public unpipe(dest?: Writable): void { + if (dest) { + this.readableStream.unpipe(dest); + } else { + this.readableStream.unpipe(); + } + } + + public destroy(error?: Error): void { + this.readableStream.destroy(error); + } + + public pause(): void { + this.readableStream.pause(); + } + + public resume(): void { + this.readableStream.resume(); + } + + public get isPaused(): boolean { + return this.readableStream.isPaused(); + } + + public async read(): Promise { + return new Promise((resolve, reject) => { + const chunk = this.readableStream.read(); + if (chunk) { + resolve(chunk); + } else { + this.readableStream.once("readable", () => { + const chunk = this.readableStream.read(); + resolve(chunk); + }); + this.readableStream.once("error", reject); + } + }); + } + + public setEncoding(encoding?: string): void { + this.readableStream.setEncoding(encoding as BufferEncoding); + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: Uint8Array[] = []; + const encoder = new TextEncoder(); + this.readableStream.setEncoding((this.encoding || "utf-8") as BufferEncoding); + + for await (const chunk of this.readableStream) { + chunks.push(encoder.encode(chunk)); + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(Buffer.concat(chunks)); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + public [Symbol.asyncIterator](): AsyncIterableIterator { + const readableStream = this.readableStream; + const iterator = readableStream[Symbol.asyncIterator](); + + // Create and return an async iterator that yields buffers + return { + async next(): Promise> { + const { value, done } = await iterator.next(); + return { value: value as Buffer, done }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts new file mode 100644 index 00000000000..263af00911f --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts @@ -0,0 +1,243 @@ +import { StreamWrapper } from "./chooseStreamWrapper"; + +type EventCallback = (data?: any) => void; + +export class UndiciStreamWrapper + implements StreamWrapper | WritableStream, ReadFormat> +{ + private readableStream: ReadableStream; + private reader: ReadableStreamDefaultReader; + private events: Record; + private paused: boolean; + private resumeCallback: ((value?: unknown) => void) | null; + private encoding: string | null; + + constructor(readableStream: ReadableStream) { + this.readableStream = readableStream; + this.reader = this.readableStream.getReader(); + this.events = { + data: [], + end: [], + error: [], + readable: [], + close: [], + pause: [], + resume: [], + }; + this.paused = false; + this.resumeCallback = null; + this.encoding = null; + } + + public on(event: string, callback: EventCallback): void { + this.events[event]?.push(callback); + } + + public off(event: string, callback: EventCallback): void { + this.events[event] = this.events[event]?.filter((cb) => cb !== callback); + } + + public pipe( + dest: UndiciStreamWrapper | WritableStream + ): UndiciStreamWrapper | WritableStream { + this.on("data", (chunk) => { + if (dest instanceof UndiciStreamWrapper) { + dest._write(chunk); + } else { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } + }); + + this.on("end", () => { + if (dest instanceof UndiciStreamWrapper) { + dest._end(); + } else { + const writer = dest.getWriter(); + writer.close(); + } + }); + + this.on("error", (error) => { + if (dest instanceof UndiciStreamWrapper) { + dest._error(error); + } else { + const writer = dest.getWriter(); + writer.abort(error); + } + }); + + this._startReading(); + + return dest; + } + + public pipeTo( + dest: UndiciStreamWrapper | WritableStream + ): UndiciStreamWrapper | WritableStream { + return this.pipe(dest); + } + + public unpipe(dest: UndiciStreamWrapper | WritableStream): void { + this.off("data", (chunk) => { + if (dest instanceof UndiciStreamWrapper) { + dest._write(chunk); + } else { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } + }); + + this.off("end", () => { + if (dest instanceof UndiciStreamWrapper) { + dest._end(); + } else { + const writer = dest.getWriter(); + writer.close(); + } + }); + + this.off("error", (error) => { + if (dest instanceof UndiciStreamWrapper) { + dest._error(error); + } else { + const writer = dest.getWriter(); + writer.abort(error); + } + }); + } + + public destroy(error?: Error): void { + this.reader + .cancel(error) + .then(() => { + this._emit("close"); + }) + .catch((err) => { + this._emit("error", err); + }); + } + + public pause(): void { + this.paused = true; + this._emit("pause"); + } + + public resume(): void { + if (this.paused) { + this.paused = false; + this._emit("resume"); + if (this.resumeCallback) { + this.resumeCallback(); + this.resumeCallback = null; + } + } + } + + public get isPaused(): boolean { + return this.paused; + } + + public async read(): Promise { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return undefined; + } + return value; + } + + public setEncoding(encoding: string): void { + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: BlobPart[] = []; + + while (true) { + const { done, value } = await this.reader.read(); + if (done) { + break; + } + if (value) { + chunks.push(value); + } + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(await new Blob(chunks).arrayBuffer()); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + private _write(chunk: ReadFormat): void { + this._emit("data", chunk); + } + + private _end(): void { + this._emit("end"); + } + + private _error(error: any): void { + this._emit("error", error); + } + + private _emit(event: string, data?: any): void { + if (this.events[event]) { + for (const callback of this.events[event] || []) { + callback(data); + } + } + } + + private async _startReading(): Promise { + try { + this._emit("readable"); + while (true) { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + this._emit("end"); + this._emit("close"); + break; + } + if (value) { + this._emit("data", value); + } + } + } catch (error) { + this._emit("error", error); + } + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + return { + next: async () => { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return { done: true, value: undefined }; + } + return { done: false, value }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts new file mode 100644 index 00000000000..2abd6b2ba1c --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts @@ -0,0 +1,33 @@ +import type { Readable } from "readable-stream"; +import { RUNTIME } from "../../runtime"; + +export type EventCallback = (data?: any) => void; + +export interface StreamWrapper { + setEncoding(encoding?: string): void; + on(event: string, callback: EventCallback): void; + off(event: string, callback: EventCallback): void; + pipe(dest: WritableStream): WritableStream; + pipeTo(dest: WritableStream): WritableStream; + unpipe(dest?: WritableStream): void; + destroy(error?: Error): void; + pause(): void; + resume(): void; + get isPaused(): boolean; + read(): Promise; + text(): Promise; + json(): Promise; + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +export async function chooseStreamWrapper(responseBody: any): Promise>> { + if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + return new (await import("./Node18UniversalStreamWrapper")).Node18UniversalStreamWrapper( + responseBody as ReadableStream + ); + } else if (RUNTIME.type !== "node" && typeof fetch === "function") { + return new (await import("./UndiciStreamWrapper")).UndiciStreamWrapper(responseBody as ReadableStream); + } else { + return new (await import("./NodePre18StreamWrapper")).NodePre18StreamWrapper(responseBody as Readable); + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/index.ts b/seed/ts-sdk/path-parameters/src/core/index.ts new file mode 100644 index 00000000000..68cce8d76d6 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/index.ts @@ -0,0 +1,4 @@ +export * from "./fetcher"; +export * from "./runtime"; +export * from "./api-promise"; +export * as serialization from "./schemas"; diff --git a/seed/ts-sdk/path-parameters/src/core/runtime/index.ts b/seed/ts-sdk/path-parameters/src/core/runtime/index.ts new file mode 100644 index 00000000000..5c76dbb133f --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/runtime/index.ts @@ -0,0 +1 @@ +export { RUNTIME } from "./runtime"; diff --git a/seed/ts-sdk/path-parameters/src/core/runtime/runtime.ts b/seed/ts-sdk/path-parameters/src/core/runtime/runtime.ts new file mode 100644 index 00000000000..4d0687e8eb4 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/runtime/runtime.ts @@ -0,0 +1,126 @@ +interface DenoGlobal { + version: { + deno: string; + }; +} + +interface BunGlobal { + version: string; +} + +declare const Deno: DenoGlobal; +declare const Bun: BunGlobal; + +/** + * A constant that indicates whether the environment the code is running is a Web Browser. + */ +const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is a Web Worker. + */ +const isWebWorker = + typeof self === "object" && + // @ts-ignore + typeof self?.importScripts === "function" && + (self.constructor?.name === "DedicatedWorkerGlobalScope" || + self.constructor?.name === "ServiceWorkerGlobalScope" || + self.constructor?.name === "SharedWorkerGlobalScope"); + +/** + * A constant that indicates whether the environment the code is running is Deno. + */ +const isDeno = + typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is Bun.sh. + */ +const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is Node.JS. + */ +const isNode = + typeof process !== "undefined" && + Boolean(process.version) && + Boolean(process.versions?.node) && + // Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions + !isDeno && + !isBun; + +/** + * A constant that indicates whether the environment the code is running is in React-Native. + * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js + */ +const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; + +/** + * A constant that indicates whether the environment the code is running is Cloudflare. + * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent + */ +const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; + +/** + * A constant that indicates which environment and version the SDK is running in. + */ +export const RUNTIME: Runtime = evaluateRuntime(); + +export interface Runtime { + type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd"; + version?: string; + parsedVersion?: number; +} + +function evaluateRuntime(): Runtime { + if (isBrowser) { + return { + type: "browser", + version: window.navigator.userAgent, + }; + } + + if (isCloudflare) { + return { + type: "workerd", + }; + } + + if (isWebWorker) { + return { + type: "web-worker", + }; + } + + if (isDeno) { + return { + type: "deno", + version: Deno.version.deno, + }; + } + + if (isBun) { + return { + type: "bun", + version: Bun.version, + }; + } + + if (isNode) { + return { + type: "node", + version: process.versions.node, + parsedVersion: Number(process.versions.node.split(".")[0]), + }; + } + + if (isReactNative) { + return { + type: "react-native", + }; + } + + return { + type: "unknown", + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/Schema.ts b/seed/ts-sdk/path-parameters/src/core/schemas/Schema.ts new file mode 100644 index 00000000000..2a72eacec99 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/Schema.ts @@ -0,0 +1,99 @@ +import { SchemaUtils } from "./builders"; + +export type Schema = BaseSchema & SchemaUtils; + +export type inferRaw = S extends Schema ? Raw : never; +export type inferParsed = S extends Schema ? Parsed : never; + +export interface BaseSchema { + parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; + json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; + getType: () => SchemaType | SchemaType; +} + +export const SchemaType = { + BIGINT: "bigint", + DATE: "date", + ENUM: "enum", + LIST: "list", + STRING_LITERAL: "stringLiteral", + BOOLEAN_LITERAL: "booleanLiteral", + OBJECT: "object", + ANY: "any", + BOOLEAN: "boolean", + NUMBER: "number", + STRING: "string", + UNKNOWN: "unknown", + RECORD: "record", + SET: "set", + UNION: "union", + UNDISCRIMINATED_UNION: "undiscriminatedUnion", + OPTIONAL: "optional", +} as const; +export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; + +export type MaybeValid = Valid | Invalid; + +export interface Valid { + ok: true; + value: T; +} + +export interface Invalid { + ok: false; + errors: ValidationError[]; +} + +export interface ValidationError { + path: string[]; + message: string; +} + +export interface SchemaOptions { + /** + * how to handle unrecognized keys in objects + * + * @default "fail" + */ + unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; + + /** + * whether to fail when an unrecognized discriminant value is + * encountered in a union + * + * @default false + */ + allowUnrecognizedUnionMembers?: boolean; + + /** + * whether to fail when an unrecognized enum value is encountered + * + * @default false + */ + allowUnrecognizedEnumValues?: boolean; + + /** + * whether to allow data that doesn't conform to the schema. + * invalid data is passed through without transformation. + * + * when this is enabled, .parse() and .json() will always + * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` + * will never fail. + * + * @default false + */ + skipValidation?: boolean; + + /** + * each validation failure contains a "path" property, which is + * the breadcrumbs to the offending node in the JSON. you can supply + * a prefix that is prepended to all the errors' paths. this can be + * helpful for zurg's internal debug logging. + */ + breadcrumbsPrefix?: string[]; + + /** + * whether to send 'null' for optional properties explicitly set to 'undefined'. + */ + omitUndefined?: boolean; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/bigint/bigint.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/bigint/bigint.ts new file mode 100644 index 00000000000..dc9c742e007 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/bigint/bigint.ts @@ -0,0 +1,50 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function bigint(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + return { + ok: true, + value: BigInt(raw), + }; + }, + json: (bigint, { breadcrumbsPrefix = [] } = {}) => { + if (typeof bigint === "bigint") { + return { + ok: true, + value: bigint.toString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(bigint, "bigint"), + }, + ], + }; + } + }, + getType: () => SchemaType.BIGINT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/bigint/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/bigint/index.ts new file mode 100644 index 00000000000..e5843043fcb --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/bigint/index.ts @@ -0,0 +1 @@ +export { bigint } from "./bigint"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/date/date.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/date/date.ts new file mode 100644 index 00000000000..b70f24b045a --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/date/date.ts @@ -0,0 +1,65 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +// https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime +const ISO_8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + +export function date(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + if (!ISO_8601_REGEX.test(raw)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), + }, + ], + }; + } + return { + ok: true, + value: new Date(raw), + }; + }, + json: (date, { breadcrumbsPrefix = [] } = {}) => { + if (date instanceof Date) { + return { + ok: true, + value: date.toISOString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(date, "Date object"), + }, + ], + }; + } + }, + getType: () => SchemaType.DATE, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/date/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/date/index.ts new file mode 100644 index 00000000000..187b29040f6 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/date/index.ts @@ -0,0 +1 @@ +export { date } from "./date"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/enum/enum.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/enum/enum.ts new file mode 100644 index 00000000000..c1e24d69dec --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/enum/enum.ts @@ -0,0 +1,43 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function enum_(values: E): Schema { + const validValues = new Set(values); + + const schemaCreator = createIdentitySchemaCreator( + SchemaType.ENUM, + (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { + if (typeof value !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + + if (!validValues.has(value) && !allowUnrecognizedEnumValues) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "enum"), + }, + ], + }; + } + + return { + ok: true, + value: value as U, + }; + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/enum/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/enum/index.ts new file mode 100644 index 00000000000..fe6faed93e3 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/enum/index.ts @@ -0,0 +1 @@ +export { enum_ } from "./enum"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/index.ts new file mode 100644 index 00000000000..65211f92522 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/index.ts @@ -0,0 +1,14 @@ +export * from "./bigint"; +export * from "./date"; +export * from "./enum"; +export * from "./lazy"; +export * from "./list"; +export * from "./literals"; +export * from "./object"; +export * from "./object-like"; +export * from "./primitives"; +export * from "./record"; +export * from "./schema-utils"; +export * from "./set"; +export * from "./undiscriminated-union"; +export * from "./union"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/index.ts new file mode 100644 index 00000000000..77420fb031c --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/index.ts @@ -0,0 +1,3 @@ +export { lazy } from "./lazy"; +export type { SchemaGetter } from "./lazy"; +export { lazyObject } from "./lazyObject"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/lazy.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/lazy.ts new file mode 100644 index 00000000000..835c61f8a56 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/lazy.ts @@ -0,0 +1,32 @@ +import { BaseSchema, Schema } from "../../Schema"; +import { getSchemaUtils } from "../schema-utils"; + +export type SchemaGetter> = () => SchemaType; + +export function lazy(getter: SchemaGetter>): Schema { + const baseSchema = constructLazyBaseSchema(getter); + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function constructLazyBaseSchema( + getter: SchemaGetter> +): BaseSchema { + return { + parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), + json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), + getType: () => getMemoizedSchema(getter).getType(), + }; +} + +type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; + +export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { + const castedGetter = getter as MemoizedGetter; + if (castedGetter.__zurg_memoized == null) { + castedGetter.__zurg_memoized = getter(); + } + return castedGetter.__zurg_memoized; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/lazyObject.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/lazyObject.ts new file mode 100644 index 00000000000..38c9e28404b --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/lazy/lazyObject.ts @@ -0,0 +1,20 @@ +import { getObjectUtils } from "../object"; +import { getObjectLikeUtils } from "../object-like"; +import { BaseObjectSchema, ObjectSchema } from "../object/types"; +import { getSchemaUtils } from "../schema-utils"; +import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; + +export function lazyObject(getter: SchemaGetter>): ObjectSchema { + const baseSchema: BaseObjectSchema = { + ...constructLazyBaseSchema(getter), + _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), + _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/list/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/list/index.ts new file mode 100644 index 00000000000..25f4bcc1737 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/list/index.ts @@ -0,0 +1 @@ +export { list } from "./list"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/list/list.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/list/list.ts new file mode 100644 index 00000000000..e4c5c4a4a99 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/list/list.ts @@ -0,0 +1,73 @@ +import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function list(schema: Schema): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => + validateAndTransformArray(raw, (item, index) => + schema.parse(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + json: (parsed, opts) => + validateAndTransformArray(parsed, (item, index) => + schema.json(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + getType: () => SchemaType.LIST, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformArray( + value: unknown, + transformItem: (item: Raw, index: number) => MaybeValid +): MaybeValid { + if (!Array.isArray(value)) { + return { + ok: false, + errors: [ + { + message: getErrorMessageForIncorrectType(value, "list"), + path: [], + }, + ], + }; + } + + const maybeValidItems = value.map((item, index) => transformItem(item, index)); + + return maybeValidItems.reduce>( + (acc, item) => { + if (acc.ok && item.ok) { + return { + ok: true, + value: [...acc.value, item.value], + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!item.ok) { + errors.push(...item.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: [] } + ); +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/booleanLiteral.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/booleanLiteral.ts new file mode 100644 index 00000000000..a83d22cd48a --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/booleanLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function booleanLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.BOOLEAN_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/index.ts new file mode 100644 index 00000000000..d2bf08fc6ca --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/index.ts @@ -0,0 +1,2 @@ +export { stringLiteral } from "./stringLiteral"; +export { booleanLiteral } from "./booleanLiteral"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/stringLiteral.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/stringLiteral.ts new file mode 100644 index 00000000000..3939b76b48d --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/literals/stringLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function stringLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.STRING_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `"${literal}"`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/getObjectLikeUtils.ts new file mode 100644 index 00000000000..8331d08da89 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -0,0 +1,79 @@ +import { BaseSchema } from "../../Schema"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { getSchemaUtils } from "../schema-utils"; +import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; + +export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { + return { + withParsedProperties: (properties) => withParsedProperties(schema, properties), + }; +} + +/** + * object-like utils are defined in one file to resolve issues with circular imports + */ + +export function withParsedProperties( + objectLike: BaseSchema, + properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } +): ObjectLikeSchema { + const objectSchema: BaseSchema = { + parse: (raw, opts) => { + const parsedObject = objectLike.parse(raw, opts); + if (!parsedObject.ok) { + return parsedObject; + } + + const additionalProperties = Object.entries(properties).reduce>( + (processed, [key, value]) => { + return { + ...processed, + [key]: typeof value === "function" ? value(parsedObject.value) : value, + }; + }, + {} + ); + + return { + ok: true, + value: { + ...parsedObject.value, + ...(additionalProperties as Properties), + }, + }; + }, + + json: (parsed, opts) => { + if (!isPlainObject(parsed)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "object"), + }, + ], + }; + } + + // strip out added properties + const addedPropertyKeys = new Set(Object.keys(properties)); + const parsedWithoutAddedProperties = filterObject( + parsed, + Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) + ); + + return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); + }, + + getType: () => objectLike.getType(), + }; + + return { + ...objectSchema, + ...getSchemaUtils(objectSchema), + ...getObjectLikeUtils(objectSchema), + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/index.ts new file mode 100644 index 00000000000..c342e72cf9d --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/index.ts @@ -0,0 +1,2 @@ +export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; +export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/types.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/types.ts new file mode 100644 index 00000000000..75b3698729c --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object-like/types.ts @@ -0,0 +1,11 @@ +import { BaseSchema, Schema } from "../../Schema"; + +export type ObjectLikeSchema = Schema & + BaseSchema & + ObjectLikeUtils; + +export interface ObjectLikeUtils { + withParsedProperties: >(properties: { + [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); + }) => ObjectLikeSchema; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/index.ts new file mode 100644 index 00000000000..e3f4388db28 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/index.ts @@ -0,0 +1,22 @@ +export { getObjectUtils, object } from "./object"; +export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; +export type { + inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, +} from "./objectWithoutOptionalProperties"; +export { isProperty, property } from "./property"; +export type { Property } from "./property"; +export type { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObject, + inferParsedObjectFromPropertySchemas, + inferParsedPropertySchema, + inferRawKey, + inferRawObject, + inferRawObjectFromPropertySchemas, + inferRawPropertySchema, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/object.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/object.ts new file mode 100644 index 00000000000..e00136d72fc --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/object.ts @@ -0,0 +1,324 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { partition } from "../../utils/partition"; +import { getObjectLikeUtils } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { isProperty } from "./property"; +import { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObjectFromPropertySchemas, + inferRawObjectFromPropertySchemas, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; + +interface ObjectPropertyWithRawKey { + rawKey: string; + parsedKey: string; + valueSchema: Schema; +} + +export function object>( + schemas: T +): inferObjectSchemaFromPropertySchemas { + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const rawKeyToProperty: Record = {}; + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + const property: ObjectPropertyWithRawKey = { + rawKey, + parsedKey: parsedKey as string, + valueSchema, + }; + + rawKeyToProperty[rawKey] = property; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(rawKey); + } + } + + return validateAndTransformObject({ + value: raw, + requiredKeys, + getProperty: (rawKey) => { + const property = rawKeyToProperty[rawKey]; + if (property == null) { + return undefined; + } + return { + transformedKey: property.parsedKey, + transform: (propertyValue) => + property.valueSchema.parse(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], + }), + }; + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + json: (parsed, opts) => { + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(parsedKey as string); + } + } + + return validateAndTransformObject({ + value: parsed, + requiredKeys, + getProperty: ( + parsedKey + ): { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined => { + const property = schemas[parsedKey as keyof T]; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property == null) { + return undefined; + } + + if (isProperty(property)) { + return { + transformedKey: property.rawKey, + transform: (propertyValue) => + property.valueSchema.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } else { + return { + transformedKey: parsedKey, + transform: (propertyValue) => + property.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + getType: () => SchemaType.OBJECT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} + +function validateAndTransformObject({ + value, + requiredKeys, + getProperty, + unrecognizedObjectKeys = "fail", + skipValidation = false, + breadcrumbsPrefix = [], +}: { + value: unknown; + requiredKeys: string[]; + getProperty: ( + preTransformedKey: string + ) => { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined; + unrecognizedObjectKeys: "fail" | "passthrough" | "strip" | undefined; + skipValidation: boolean | undefined; + breadcrumbsPrefix: string[] | undefined; + omitUndefined: boolean | undefined; +}): MaybeValid { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const missingRequiredKeys = new Set(requiredKeys); + const errors: ValidationError[] = []; + const transformed: Record = {}; + + for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + const property = getProperty(preTransformedKey); + + if (property != null) { + missingRequiredKeys.delete(preTransformedKey); + + const value = property.transform(preTransformedItemValue); + if (value.ok) { + transformed[property.transformedKey] = value.value; + } else { + transformed[preTransformedKey] = preTransformedItemValue; + errors.push(...value.errors); + } + } else { + switch (unrecognizedObjectKeys) { + case "fail": + errors.push({ + path: [...breadcrumbsPrefix, preTransformedKey], + message: `Unexpected key "${preTransformedKey}"`, + }); + break; + case "strip": + break; + case "passthrough": + transformed[preTransformedKey] = preTransformedItemValue; + break; + } + } + } + + errors.push( + ...requiredKeys + .filter((key) => missingRequiredKeys.has(key)) + .map((key) => ({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + })) + ); + + if (errors.length === 0 || skipValidation) { + return { + ok: true, + value: transformed as Transformed, + }; + } else { + return { + ok: false, + errors, + }; + } +} + +export function getObjectUtils(schema: BaseObjectSchema): ObjectUtils { + return { + extend: (extension: ObjectSchema) => { + const baseSchema: BaseObjectSchema = { + _getParsedProperties: () => [...schema._getParsedProperties(), ...extension._getParsedProperties()], + _getRawProperties: () => [...schema._getRawProperties(), ...extension._getRawProperties()], + parse: (raw, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getRawProperties(), + value: raw, + transformBase: (rawBase) => schema.parse(rawBase, opts), + transformExtension: (rawExtension) => extension.parse(rawExtension, opts), + }); + }, + json: (parsed, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getParsedProperties(), + value: parsed, + transformBase: (parsedBase) => schema.json(parsedBase, opts), + transformExtension: (parsedExtension) => extension.json(parsedExtension, opts), + }); + }, + getType: () => SchemaType.OBJECT, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; + }, + }; +} + +function validateAndTransformExtendedObject({ + extensionKeys, + value, + transformBase, + transformExtension, +}: { + extensionKeys: (keyof PreTransformedExtension)[]; + value: unknown; + transformBase: (value: unknown) => MaybeValid; + transformExtension: (value: unknown) => MaybeValid; +}): MaybeValid { + const extensionPropertiesSet = new Set(extensionKeys); + const [extensionProperties, baseProperties] = partition(keys(value), (key) => + extensionPropertiesSet.has(key as keyof PreTransformedExtension) + ); + + const transformedBase = transformBase(filterObject(value, baseProperties)); + const transformedExtension = transformExtension(filterObject(value, extensionProperties)); + + if (transformedBase.ok && transformedExtension.ok) { + return { + ok: true, + value: { + ...transformedBase.value, + ...transformedExtension.value, + }, + }; + } else { + return { + ok: false, + errors: [ + ...(transformedBase.ok ? [] : transformedBase.errors), + ...(transformedExtension.ok ? [] : transformedExtension.errors), + ], + }; + } +} + +function isSchemaRequired(schema: Schema): boolean { + return !isSchemaOptional(schema); +} + +function isSchemaOptional(schema: Schema): boolean { + switch (schema.getType()) { + case SchemaType.ANY: + case SchemaType.UNKNOWN: + case SchemaType.OPTIONAL: + return true; + default: + return false; + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts new file mode 100644 index 00000000000..a0951f48efc --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts @@ -0,0 +1,18 @@ +import { object } from "./object"; +import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; + +export function objectWithoutOptionalProperties>( + schemas: T +): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { + return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; +} + +export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = + ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas + >; + +export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { + [K in keyof T]: inferParsedPropertySchema; +}; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/property.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/property.ts new file mode 100644 index 00000000000..d245c4b193a --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/property.ts @@ -0,0 +1,23 @@ +import { Schema } from "../../Schema"; + +export function property( + rawKey: RawKey, + valueSchema: Schema +): Property { + return { + rawKey, + valueSchema, + isProperty: true, + }; +} + +export interface Property { + rawKey: RawKey; + valueSchema: Schema; + isProperty: true; +} + +export function isProperty>(maybeProperty: unknown): maybeProperty is O { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (maybeProperty as O).isProperty; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/types.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/types.ts new file mode 100644 index 00000000000..de9bb4074e5 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/object/types.ts @@ -0,0 +1,72 @@ +import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; +import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; +import { ObjectLikeUtils } from "../object-like"; +import { SchemaUtils } from "../schema-utils"; +import { Property } from "./property"; + +export type ObjectSchema = BaseObjectSchema & + ObjectLikeUtils & + ObjectUtils & + SchemaUtils; + +export interface BaseObjectSchema extends BaseSchema { + _getRawProperties: () => (keyof Raw)[]; + _getParsedProperties: () => (keyof Parsed)[]; +} + +export interface ObjectUtils { + extend: ( + schemas: ObjectSchema + ) => ObjectSchema; +} + +export type inferRawObject> = O extends ObjectSchema ? Raw : never; + +export type inferParsedObject> = O extends ObjectSchema + ? Parsed + : never; + +export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas +>; + +export type inferRawObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; + }>; + +export type inferParsedObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [K in keyof T]: inferParsedPropertySchema; + }>; + +export type PropertySchemas = Record< + ParsedKeys, + Property | Schema +>; + +export type inferRawPropertySchema

| Schema> = P extends Property< + any, + infer Raw, + any +> + ? Raw + : P extends Schema + ? inferRaw

+ : never; + +export type inferParsedPropertySchema

| Schema> = P extends Property< + any, + any, + infer Parsed +> + ? Parsed + : P extends Schema + ? inferParsed

+ : never; + +export type inferRawKey< + ParsedKey extends string | number | symbol, + P extends Property | Schema +> = P extends Property ? Raw : ParsedKey; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/any.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/any.ts new file mode 100644 index 00000000000..fcaeb04255a --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/any.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/boolean.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/boolean.ts new file mode 100644 index 00000000000..fad60562120 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/boolean.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const boolean = createIdentitySchemaCreator( + SchemaType.BOOLEAN, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "boolean") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "boolean"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/index.ts new file mode 100644 index 00000000000..788f9416bfe --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/index.ts @@ -0,0 +1,5 @@ +export { any } from "./any"; +export { boolean } from "./boolean"; +export { number } from "./number"; +export { string } from "./string"; +export { unknown } from "./unknown"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/number.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/number.ts new file mode 100644 index 00000000000..c2689456936 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/number.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const number = createIdentitySchemaCreator( + SchemaType.NUMBER, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "number") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "number"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/string.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/string.ts new file mode 100644 index 00000000000..949f1f2a630 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/string.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const string = createIdentitySchemaCreator( + SchemaType.STRING, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "string") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/unknown.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/unknown.ts new file mode 100644 index 00000000000..4d5249571f5 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/primitives/unknown.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/index.ts new file mode 100644 index 00000000000..82e25c5c2af --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/index.ts @@ -0,0 +1,2 @@ +export { record } from "./record"; +export type { BaseRecordSchema, RecordSchema } from "./types"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/record.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/record.ts new file mode 100644 index 00000000000..6683ac3609f --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/record.ts @@ -0,0 +1,130 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { BaseRecordSchema, RecordSchema } from "./types"; + +export function record( + keySchema: Schema, + valueSchema: Schema +): RecordSchema { + const baseSchema: BaseRecordSchema = { + parse: (raw, opts) => { + return validateAndTransformRecord({ + value: raw, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.parse(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.parse(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return validateAndTransformRecord({ + value: parsed, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.json(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.json(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.RECORD, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformRecord({ + value, + isKeyNumeric, + transformKey, + transformValue, + breadcrumbsPrefix = [], +}: { + value: unknown; + isKeyNumeric: boolean; + transformKey: (key: string | number) => MaybeValid; + transformValue: (value: unknown, key: string | number) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + return entries(value).reduce>>( + (accPromise, [stringKey, value]) => { + // skip nullish keys + if (value == null) { + return accPromise; + } + + const acc = accPromise; + + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!isNaN(numberKey)) { + key = numberKey; + } + } + const transformedKey = transformKey(key); + + const transformedValue = transformValue(value, key); + + if (acc.ok && transformedKey.ok && transformedValue.ok) { + return { + ok: true, + value: { + ...acc.value, + [transformedKey.value]: transformedValue.value, + }, + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!transformedKey.ok) { + errors.push(...transformedKey.errors); + } + if (!transformedValue.ok) { + errors.push(...transformedValue.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: {} as Record } + ); +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/types.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/types.ts new file mode 100644 index 00000000000..eb82cc7f65c --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/record/types.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from "../../Schema"; +import { SchemaUtils } from "../schema-utils"; + +export type RecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseRecordSchema & + SchemaUtils, Record>; + +export type BaseRecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseSchema, Record>; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/JsonError.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/JsonError.ts new file mode 100644 index 00000000000..2b89ca0e7ad --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/JsonError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class JsonError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, JsonError.prototype); + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/ParseError.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/ParseError.ts new file mode 100644 index 00000000000..d056eb45cf7 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/ParseError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class ParseError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, ParseError.prototype); + } +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/getSchemaUtils.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/getSchemaUtils.ts new file mode 100644 index 00000000000..79ecad92132 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/getSchemaUtils.ts @@ -0,0 +1,105 @@ +import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; +import { JsonError } from "./JsonError"; +import { ParseError } from "./ParseError"; + +export interface SchemaUtils { + optional: () => Schema; + transform: (transformer: SchemaTransformer) => Schema; + parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; + jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; +} + +export interface SchemaTransformer { + transform: (parsed: Parsed) => Transformed; + untransform: (transformed: any) => Parsed; +} + +export function getSchemaUtils(schema: BaseSchema): SchemaUtils { + return { + optional: () => optional(schema), + transform: (transformer) => transform(schema, transformer), + parseOrThrow: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (parsed.ok) { + return parsed.value; + } + throw new ParseError(parsed.errors); + }, + jsonOrThrow: (parsed, opts) => { + const raw = schema.json(parsed, opts); + if (raw.ok) { + return raw.value; + } + throw new JsonError(raw.errors); + }, + }; +} + +/** + * schema utils are defined in one file to resolve issues with circular imports + */ + +export function optional( + schema: BaseSchema +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + if (raw == null) { + return { + ok: true, + value: undefined, + }; + } + return schema.parse(raw, opts); + }, + json: (parsed, opts) => { + if (opts?.omitUndefined && parsed === undefined) { + return { + ok: true, + value: undefined, + }; + } + if (parsed == null) { + return { + ok: true, + value: null, + }; + } + return schema.json(parsed, opts); + }, + getType: () => SchemaType.OPTIONAL, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function transform( + schema: BaseSchema, + transformer: SchemaTransformer +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (!parsed.ok) { + return parsed; + } + return { + ok: true, + value: transformer.transform(parsed.value), + }; + }, + json: (transformed, opts) => { + const parsed = transformer.untransform(transformed); + return schema.json(parsed, opts); + }, + getType: () => schema.getType(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/index.ts new file mode 100644 index 00000000000..aa04e051dfa --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/index.ts @@ -0,0 +1,4 @@ +export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; +export type { SchemaUtils } from "./getSchemaUtils"; +export { JsonError } from "./JsonError"; +export { ParseError } from "./ParseError"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts new file mode 100644 index 00000000000..4160f0a2617 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts @@ -0,0 +1,8 @@ +import { ValidationError } from "../../Schema"; + +export function stringifyValidationError(error: ValidationError): string { + if (error.path.length === 0) { + return error.message; + } + return `${error.path.join(" -> ")}: ${error.message}`; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/set/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/set/index.ts new file mode 100644 index 00000000000..f3310e8bdad --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/set/index.ts @@ -0,0 +1 @@ +export { set } from "./set"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/set/set.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/set/set.ts new file mode 100644 index 00000000000..e9e6bb7e539 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/set/set.ts @@ -0,0 +1,43 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { list } from "../list"; +import { getSchemaUtils } from "../schema-utils"; + +export function set(schema: Schema): Schema> { + const listSchema = list(schema); + const baseSchema: BaseSchema> = { + parse: (raw, opts) => { + const parsedList = listSchema.parse(raw, opts); + if (parsedList.ok) { + return { + ok: true, + value: new Set(parsedList.value), + }; + } else { + return parsedList; + } + }, + json: (parsed, opts) => { + if (!(parsed instanceof Set)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "Set"), + }, + ], + }; + } + const jsonList = listSchema.json([...parsed], opts); + return jsonList; + }, + getType: () => SchemaType.SET, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/index.ts new file mode 100644 index 00000000000..75b71cb3565 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/index.ts @@ -0,0 +1,6 @@ +export type { + inferParsedUnidiscriminatedUnionSchema, + inferRawUnidiscriminatedUnionSchema, + UndiscriminatedUnionSchema, +} from "./types"; +export { undiscriminatedUnion } from "./undiscriminatedUnion"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/types.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/types.ts new file mode 100644 index 00000000000..43e7108a060 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/types.ts @@ -0,0 +1,10 @@ +import { inferParsed, inferRaw, Schema } from "../../Schema"; + +export type UndiscriminatedUnionSchema = Schema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema +>; + +export type inferRawUnidiscriminatedUnionSchema = inferRaw; + +export type inferParsedUnidiscriminatedUnionSchema = inferParsed; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts new file mode 100644 index 00000000000..21ed3df0f40 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts @@ -0,0 +1,60 @@ +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; + +export function undiscriminatedUnion, ...Schema[]]>( + schemas: Schemas +): Schema, inferParsedUnidiscriminatedUnionSchema> { + const baseSchema: BaseSchema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema + > = { + parse: (raw, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.parse(raw, opts), + schemas, + opts + ); + }, + json: (parsed, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.json(parsed, opts), + schemas, + opts + ); + }, + getType: () => SchemaType.UNDISCRIMINATED_UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformUndiscriminatedUnion( + transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, + schemas: Schema[], + opts: SchemaOptions | undefined +): MaybeValid { + const errors: ValidationError[] = []; + for (const [index, schema] of schemas.entries()) { + const transformed = transform(schema, { ...opts, skipValidation: false }); + if (transformed.ok) { + return transformed; + } else { + for (const error of transformed.errors) { + errors.push({ + path: error.path, + message: `[Variant ${index}] ${error.message}`, + }); + } + } + } + + return { + ok: false, + errors, + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/discriminant.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/discriminant.ts new file mode 100644 index 00000000000..55065bc8946 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/discriminant.ts @@ -0,0 +1,14 @@ +export function discriminant( + parsedDiscriminant: ParsedDiscriminant, + rawDiscriminant: RawDiscriminant +): Discriminant { + return { + parsedDiscriminant, + rawDiscriminant, + }; +} + +export interface Discriminant { + parsedDiscriminant: ParsedDiscriminant; + rawDiscriminant: RawDiscriminant; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/index.ts new file mode 100644 index 00000000000..85fc008a2d8 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/index.ts @@ -0,0 +1,10 @@ +export { discriminant } from "./discriminant"; +export type { Discriminant } from "./discriminant"; +export type { + inferParsedDiscriminant, + inferParsedUnion, + inferRawDiscriminant, + inferRawUnion, + UnionSubtypes, +} from "./types"; +export { union } from "./union"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/types.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/types.ts new file mode 100644 index 00000000000..6f82c868b2d --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/types.ts @@ -0,0 +1,26 @@ +import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; +import { Discriminant } from "./discriminant"; + +export type UnionSubtypes = { + [K in DiscriminantValues]: ObjectSchema; +}; + +export type inferRawUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferRawObject; +}[keyof U]; + +export type inferParsedUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferParsedObject; +}[keyof U]; + +export type inferRawDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Raw + : never; + +export type inferParsedDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Parsed + : never; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/union.ts b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/union.ts new file mode 100644 index 00000000000..ab61475a572 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/builders/union/union.ts @@ -0,0 +1,170 @@ +import { BaseSchema, MaybeValid, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { enum_ } from "../enum"; +import { ObjectSchema } from "../object"; +import { getObjectLikeUtils, ObjectLikeSchema } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { Discriminant } from "./discriminant"; +import { inferParsedDiscriminant, inferParsedUnion, inferRawDiscriminant, inferRawUnion, UnionSubtypes } from "./types"; + +export function union, U extends UnionSubtypes>( + discriminant: D, + union: U +): ObjectLikeSchema, inferParsedUnion> { + const rawDiscriminant = + typeof discriminant === "string" ? discriminant : (discriminant.rawDiscriminant as inferRawDiscriminant); + const parsedDiscriminant = + typeof discriminant === "string" + ? discriminant + : (discriminant.parsedDiscriminant as inferParsedDiscriminant); + + const discriminantValueSchema = enum_(keys(union) as string[]); + + const baseSchema: BaseSchema, inferParsedUnion> = { + parse: (raw, opts) => { + return transformAndValidateUnion({ + value: raw, + discriminant: rawDiscriminant, + transformedDiscriminant: parsedDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.parse(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.parse(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return transformAndValidateUnion({ + value: parsed, + discriminant: parsedDiscriminant, + transformedDiscriminant: rawDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.json(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.json(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + }; +} + +function transformAndValidateUnion< + TransformedDiscriminant extends string, + TransformedDiscriminantValue extends string, + TransformedAdditionalProperties +>({ + value, + discriminant, + transformedDiscriminant, + transformDiscriminantValue, + getAdditionalPropertiesSchema, + allowUnrecognizedUnionMembers = false, + transformAdditionalProperties, + breadcrumbsPrefix = [], +}: { + value: unknown; + discriminant: string; + transformedDiscriminant: TransformedDiscriminant; + transformDiscriminantValue: (discriminantValue: unknown) => MaybeValid; + getAdditionalPropertiesSchema: (discriminantValue: string) => ObjectSchema | undefined; + allowUnrecognizedUnionMembers: boolean | undefined; + transformAdditionalProperties: ( + additionalProperties: unknown, + additionalPropertiesSchema: ObjectSchema + ) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid & TransformedAdditionalProperties> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const { [discriminant]: discriminantValue, ...additionalProperties } = value; + + if (discriminantValue == null) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: `Missing discriminant ("${discriminant}")`, + }, + ], + }; + } + + const transformedDiscriminantValue = transformDiscriminantValue(discriminantValue); + if (!transformedDiscriminantValue.ok) { + return { + ok: false, + errors: transformedDiscriminantValue.errors, + }; + } + + const additionalPropertiesSchema = getAdditionalPropertiesSchema(transformedDiscriminantValue.value); + + if (additionalPropertiesSchema == null) { + if (allowUnrecognizedUnionMembers) { + return { + ok: true, + value: { + [transformedDiscriminant]: transformedDiscriminantValue.value, + ...additionalProperties, + } as Record & TransformedAdditionalProperties, + }; + } else { + return { + ok: false, + errors: [ + { + path: [...breadcrumbsPrefix, discriminant], + message: "Unexpected discriminant value", + }, + ], + }; + } + } + + const transformedAdditionalProperties = transformAdditionalProperties( + additionalProperties, + additionalPropertiesSchema + ); + if (!transformedAdditionalProperties.ok) { + return transformedAdditionalProperties; + } + + return { + ok: true, + value: { + [transformedDiscriminant]: discriminantValue, + ...transformedAdditionalProperties.value, + } as Record & TransformedAdditionalProperties, + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/index.ts b/seed/ts-sdk/path-parameters/src/core/schemas/index.ts new file mode 100644 index 00000000000..5429d8b43eb --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./builders"; +export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/MaybePromise.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/MaybePromise.ts new file mode 100644 index 00000000000..9cd354b3418 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/MaybePromise.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts new file mode 100644 index 00000000000..4111d703cd0 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts @@ -0,0 +1,15 @@ +export type addQuestionMarksToNullableProperties = { + [K in OptionalKeys]?: T[K]; +} & Pick>; + +export type OptionalKeys = { + [K in keyof T]-?: undefined extends T[K] + ? K + : null extends T[K] + ? K + : 1 extends (any extends T[K] ? 0 : 1) + ? never + : K; +}[keyof T]; + +export type RequiredKeys = Exclude>; diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/createIdentitySchemaCreator.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/createIdentitySchemaCreator.ts new file mode 100644 index 00000000000..de107cf5ee1 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/createIdentitySchemaCreator.ts @@ -0,0 +1,21 @@ +import { getSchemaUtils } from "../builders/schema-utils"; +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; +import { maybeSkipValidation } from "./maybeSkipValidation"; + +export function createIdentitySchemaCreator( + schemaType: SchemaType, + validate: (value: unknown, opts?: SchemaOptions) => MaybeValid +): () => Schema { + return () => { + const baseSchema: BaseSchema = { + parse: validate, + json: validate, + getType: () => schemaType, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/entries.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/entries.ts new file mode 100644 index 00000000000..e122952137d --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/entries.ts @@ -0,0 +1,3 @@ +export function entries(object: T): [keyof T, T[keyof T]][] { + return Object.entries(object) as [keyof T, T[keyof T]][]; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/filterObject.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/filterObject.ts new file mode 100644 index 00000000000..2c25a3455bc --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/filterObject.ts @@ -0,0 +1,10 @@ +export function filterObject(obj: T, keysToInclude: K[]): Pick { + const keysToIncludeSet = new Set(keysToInclude); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (keysToIncludeSet.has(key as K)) { + acc[key as K] = value; + } + return acc; + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + }, {} as Pick); +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/getErrorMessageForIncorrectType.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/getErrorMessageForIncorrectType.ts new file mode 100644 index 00000000000..1a5c31027ce --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/getErrorMessageForIncorrectType.ts @@ -0,0 +1,25 @@ +export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { + return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; +} + +function getTypeAsString(value: unknown): string { + if (Array.isArray(value)) { + return "list"; + } + if (value === null) { + return "null"; + } + if (value instanceof BigInt) { + return "BigInt"; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "bigint": + case "number": + case "boolean": + case "undefined": + return `${value}`; + } + return typeof value; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/isPlainObject.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/isPlainObject.ts new file mode 100644 index 00000000000..db82a722c35 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/isPlainObject.ts @@ -0,0 +1,17 @@ +// borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js +export function isPlainObject(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + if (Object.getPrototypeOf(value) === null) { + return true; + } + + let proto = value; + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + + return Object.getPrototypeOf(value) === proto; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/keys.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/keys.ts new file mode 100644 index 00000000000..01867098287 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/keys.ts @@ -0,0 +1,3 @@ +export function keys(object: T): (keyof T)[] { + return Object.keys(object) as (keyof T)[]; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/maybeSkipValidation.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/maybeSkipValidation.ts new file mode 100644 index 00000000000..86c07abf2b4 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/maybeSkipValidation.ts @@ -0,0 +1,38 @@ +import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; + +export function maybeSkipValidation, Raw, Parsed>(schema: S): S { + return { + ...schema, + json: transformAndMaybeSkipValidation(schema.json), + parse: transformAndMaybeSkipValidation(schema.parse), + }; +} + +function transformAndMaybeSkipValidation( + transform: (value: unknown, opts?: SchemaOptions) => MaybeValid +): (value: unknown, opts?: SchemaOptions) => MaybeValid { + return (value, opts): MaybeValid => { + const transformed = transform(value, opts); + const { skipValidation = false } = opts ?? {}; + if (!transformed.ok && skipValidation) { + // eslint-disable-next-line no-console + console.warn( + [ + "Failed to validate.", + ...transformed.errors.map( + (error) => + " - " + + (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) + ), + ].join("\n") + ); + + return { + ok: true, + value: value as T, + }; + } else { + return transformed; + } + }; +} diff --git a/seed/ts-sdk/path-parameters/src/core/schemas/utils/partition.ts b/seed/ts-sdk/path-parameters/src/core/schemas/utils/partition.ts new file mode 100644 index 00000000000..f58d6f3d35f --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/core/schemas/utils/partition.ts @@ -0,0 +1,12 @@ +export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { + const trueItems: T[] = [], + falseItems: T[] = []; + for (const item of items) { + if (predicate(item)) { + trueItems.push(item); + } else { + falseItems.push(item); + } + } + return [trueItems, falseItems]; +} diff --git a/seed/ts-sdk/path-parameters/src/errors/SeedPathParametersError.ts b/seed/ts-sdk/path-parameters/src/errors/SeedPathParametersError.ts new file mode 100644 index 00000000000..a235eaf3662 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/errors/SeedPathParametersError.ts @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export class SeedPathParametersError extends Error { + readonly statusCode?: number; + readonly body?: unknown; + + constructor({ message, statusCode, body }: { message?: string; statusCode?: number; body?: unknown }) { + super(buildMessage({ message, statusCode, body })); + Object.setPrototypeOf(this, SeedPathParametersError.prototype); + if (statusCode != null) { + this.statusCode = statusCode; + } + + if (body !== undefined) { + this.body = body; + } + } +} + +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { + let lines: string[] = []; + if (message != null) { + lines.push(message); + } + + if (statusCode != null) { + lines.push(`Status code: ${statusCode.toString()}`); + } + + if (body != null) { + lines.push(`Body: ${JSON.stringify(body, undefined, 2)}`); + } + + return lines.join("\n"); +} diff --git a/seed/ts-sdk/path-parameters/src/errors/SeedPathParametersTimeoutError.ts b/seed/ts-sdk/path-parameters/src/errors/SeedPathParametersTimeoutError.ts new file mode 100644 index 00000000000..3c84543b1fe --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/errors/SeedPathParametersTimeoutError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export class SeedPathParametersTimeoutError extends Error { + constructor() { + super("Timeout"); + Object.setPrototypeOf(this, SeedPathParametersTimeoutError.prototype); + } +} diff --git a/seed/ts-sdk/path-parameters/src/errors/index.ts b/seed/ts-sdk/path-parameters/src/errors/index.ts new file mode 100644 index 00000000000..09d4c704cf2 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/errors/index.ts @@ -0,0 +1,2 @@ +export { SeedPathParametersError } from "./SeedPathParametersError"; +export { SeedPathParametersTimeoutError } from "./SeedPathParametersTimeoutError"; diff --git a/seed/ts-sdk/path-parameters/src/index.ts b/seed/ts-sdk/path-parameters/src/index.ts new file mode 100644 index 00000000000..e5a2dd5aebc --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/index.ts @@ -0,0 +1,3 @@ +export * as SeedPathParameters from "./api"; +export { SeedPathParametersClient } from "./Client"; +export { SeedPathParametersError, SeedPathParametersTimeoutError } from "./errors"; diff --git a/seed/ts-sdk/path-parameters/src/serialization/index.ts b/seed/ts-sdk/path-parameters/src/serialization/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/serialization/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-sdk/path-parameters/src/serialization/resources/index.ts b/seed/ts-sdk/path-parameters/src/serialization/resources/index.ts new file mode 100644 index 00000000000..e2e6c212efb --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/serialization/resources/index.ts @@ -0,0 +1,2 @@ +export * as user from "./user"; +export * from "./user/types"; diff --git a/seed/ts-sdk/path-parameters/src/serialization/resources/user/index.ts b/seed/ts-sdk/path-parameters/src/serialization/resources/user/index.ts new file mode 100644 index 00000000000..eea524d6557 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/serialization/resources/user/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/seed/ts-sdk/path-parameters/src/serialization/resources/user/types/User.ts b/seed/ts-sdk/path-parameters/src/serialization/resources/user/types/User.ts new file mode 100644 index 00000000000..f85255d90a7 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/serialization/resources/user/types/User.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedPathParameters from "../../../../api/index"; +import * as core from "../../../../core"; + +export const User: core.serialization.ObjectSchema = + core.serialization.object({ + name: core.serialization.string(), + tags: core.serialization.list(core.serialization.string()), + }); + +export declare namespace User { + interface Raw { + name: string; + tags: string[]; + } +} diff --git a/seed/ts-sdk/path-parameters/src/serialization/resources/user/types/index.ts b/seed/ts-sdk/path-parameters/src/serialization/resources/user/types/index.ts new file mode 100644 index 00000000000..3ce758c1197 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/serialization/resources/user/types/index.ts @@ -0,0 +1 @@ +export * from "./User"; diff --git a/seed/ts-sdk/path-parameters/src/version.ts b/seed/ts-sdk/path-parameters/src/version.ts new file mode 100644 index 00000000000..b643a3e3ea2 --- /dev/null +++ b/seed/ts-sdk/path-parameters/src/version.ts @@ -0,0 +1 @@ +export const SDK_VERSION = "0.0.1"; diff --git a/seed/ts-sdk/path-parameters/tests/custom.test.ts b/seed/ts-sdk/path-parameters/tests/custom.test.ts new file mode 100644 index 00000000000..7f5e031c839 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/custom.test.ts @@ -0,0 +1,13 @@ +/** + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ +describe("test", () => { + it("default", () => { + expect(true).toBe(true); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/Fetcher.test.ts new file mode 100644 index 00000000000..ff8042175ff --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/Fetcher.test.ts @@ -0,0 +1,73 @@ +import fs from "fs"; +import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; +import { join } from "path"; + +describe("Test fetcherImpl", () => { + it("should handle successful request", async () => { + const mockArgs: Fetcher.Args = { + url: "https://httpbin.org/post", + method: "POST", + headers: { "X-Test": "x-test-header" }, + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + }; + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify({ data: "test" })), + json: () => ({ data: "test" }), + }); + + const result = await fetcherImpl(mockArgs); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + + expect(global.fetch).toHaveBeenCalledWith( + "https://httpbin.org/post", + expect.objectContaining({ + method: "POST", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + body: JSON.stringify({ data: "test" }), + }) + ); + }); + + it("should send octet stream", async () => { + const url = "https://httpbin.org/post/file"; + const mockArgs: Fetcher.Args = { + url, + method: "POST", + headers: { "X-Test": "x-test-header" }, + contentType: "application/octet-stream", + requestType: "bytes", + duplex: "half", + body: fs.createReadStream(join(__dirname, "test-file.txt")), + }; + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify({ data: "test" })), + json: () => Promise.resolve({ data: "test" }), + }); + + const result = await fetcherImpl(mockArgs); + + expect(global.fetch).toHaveBeenCalledWith( + url, + expect.objectContaining({ + method: "POST", + headers: expect.objectContaining({ "X-Test": "x-test-header" }), + body: expect.any(fs.ReadStream), + }) + ); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/createRequestUrl.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/createRequestUrl.test.ts new file mode 100644 index 00000000000..f2cd24b6721 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/createRequestUrl.test.ts @@ -0,0 +1,51 @@ +import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; + +describe("Test createRequestUrl", () => { + it("should return the base URL when no query parameters are provided", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl)).toBe(baseUrl); + }); + + it("should append simple query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { key: "value", another: "param" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?key=value&another=param"); + }); + + it("should handle array query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { items: ["a", "b", "c"] }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?items=a&items=b&items=c"); + }); + + it("should handle object query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { filter: { name: "John", age: 30 } }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30" + ); + }); + + it("should handle mixed types of query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + simple: "value", + array: ["x", "y"], + object: { key: "value" }, + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value" + ); + }); + + it("should handle empty query parameters object", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl, {})).toBe(baseUrl); + }); + + it("should encode special characters in query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { special: "a&b=c d" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?special=a%26b%3Dc%20d"); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/getFetchFn.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/getFetchFn.test.ts new file mode 100644 index 00000000000..9b315ad095a --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/getFetchFn.test.ts @@ -0,0 +1,22 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getFetchFn } from "../../../src/core/fetcher/getFetchFn"; + +describe("Test for getFetchFn", () => { + it("should get node-fetch function", async () => { + if (RUNTIME.type == "node") { + if (RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + expect(await getFetchFn()).toBe(fetch); + } else { + expect(await getFetchFn()).toEqual((await import("node-fetch")).default as any); + } + } + }); + + it("should get fetch function", async () => { + if (RUNTIME.type == "browser") { + const fetchFn = await getFetchFn(); + expect(typeof fetchFn).toBe("function"); + expect(fetchFn.name).toBe("fetch"); + } + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/getRequestBody.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/getRequestBody.test.ts new file mode 100644 index 00000000000..919604c2e12 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/getRequestBody.test.ts @@ -0,0 +1,77 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; + +describe("Test getRequestBody", () => { + it("should return FormData as is in Node environment", async () => { + if (RUNTIME.type === "node") { + const formData = new (await import("formdata-node")).FormData(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); + + it("should stringify body if not FormData in Node environment", async () => { + if (RUNTIME.type === "node") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const formData = new (await import("form-data")).default(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); + + it("should stringify body if not FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return the Uint8Array", async () => { + const input = new Uint8Array([1, 2, 3]); + const result = await getRequestBody({ + body: input, + type: "bytes", + }); + expect(result).toBe(input); + }); + + it("should return the input for content-type 'application/x-www-form-urlencoded'", async () => { + const input = "key=value&another=param"; + const result = await getRequestBody({ + body: input, + type: "other", + }); + expect(result).toBe(input); + }); + + it("should JSON stringify objects", async () => { + const input = { key: "value" }; + const result = await getRequestBody({ + body: input, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/getResponseBody.test.ts new file mode 100644 index 00000000000..1030c517ced --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/getResponseBody.test.ts @@ -0,0 +1,64 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; +import { chooseStreamWrapper } from "../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; + +describe("Test getResponseBody", () => { + it("should handle blob response type", async () => { + const mockBlob = new Blob(["test"], { type: "text/plain" }); + const mockResponse = new Response(mockBlob); + const result = await getResponseBody(mockResponse, "blob"); + // @ts-expect-error + expect(result.constructor.name).toBe("Blob"); + }); + + it("should handle sse response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "sse"); + expect(result).toBe(mockStream); + } + }); + + it("should handle streaming response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "streaming"); + // need to reinstantiate string as a result of locked state in Readable Stream after registration with Response + expect(JSON.stringify(result)).toBe(JSON.stringify(await chooseStreamWrapper(new ReadableStream()))); + } + }); + + it("should handle text response type", async () => { + const mockResponse = new Response("test text"); + const result = await getResponseBody(mockResponse, "text"); + expect(result).toBe("test text"); + }); + + it("should handle JSON response", async () => { + const mockJson = { key: "value" }; + const mockResponse = new Response(JSON.stringify(mockJson)); + const result = await getResponseBody(mockResponse); + expect(result).toEqual(mockJson); + }); + + it("should handle empty response", async () => { + const mockResponse = new Response(""); + const result = await getResponseBody(mockResponse); + expect(result).toBeUndefined(); + }); + + it("should handle non-JSON response", async () => { + const mockResponse = new Response("invalid json"); + const result = await getResponseBody(mockResponse); + expect(result).toEqual({ + ok: false, + error: { + reason: "non-json", + statusCode: 200, + rawBody: "invalid json", + }, + }); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/makeRequest.test.ts new file mode 100644 index 00000000000..be94ab45f10 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/makeRequest.test.ts @@ -0,0 +1,54 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { makeRequest } from "../../../src/core/fetcher/makeRequest"; + +describe("Test makeRequest", () => { + const mockPostUrl = "https://httpbin.org/post"; + const mockGetUrl = "https://httpbin.org/get"; + const mockHeaders = { "Content-Type": "application/json" }; + const mockBody = JSON.stringify({ key: "value" }); + + let mockFetch: jest.Mock; + + beforeEach(() => { + mockFetch = jest.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); + }); + + it("should handle POST request correctly", async () => { + const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockPostUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "POST", + headers: mockHeaders, + body: mockBody, + credentials: undefined, + }) + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should handle GET request correctly", async () => { + const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockGetUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "GET", + headers: mockHeaders, + body: undefined, + credentials: undefined, + }) + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/requestWithRetries.test.ts new file mode 100644 index 00000000000..6f77f093edf --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/requestWithRetries.test.ts @@ -0,0 +1,133 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; + +describe("requestWithRetries", () => { + let mockFetch: jest.Mock; + let originalMathRandom: typeof Math.random; + let setTimeoutSpy: jest.SpyInstance; + + beforeEach(() => { + mockFetch = jest.fn(); + originalMathRandom = Math.random; + + // Mock Math.random for consistent jitter + Math.random = jest.fn(() => 0.5); + + jest.useFakeTimers({ doNotFake: ["nextTick"] }); + }); + + afterEach(() => { + Math.random = originalMathRandom; + jest.clearAllMocks(); + jest.clearAllTimers(); + }); + + it("should retry on retryable status codes", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback) => { + process.nextTick(callback); + return null as any; + }); + + const retryableStatuses = [408, 409, 429, 500, 502]; + let callCount = 0; + + mockFetch.mockImplementation(async () => { + if (callCount < retryableStatuses.length) { + return new Response("", { status: retryableStatuses[callCount++] }); + } + return new Response("", { status: 200 }); + }); + + const responsePromise = requestWithRetries(() => mockFetch(), retryableStatuses.length); + await jest.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(retryableStatuses.length + 1); + expect(response.status).toBe(200); + }); + + it("should respect maxRetries limit", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback) => { + process.nextTick(callback); + return null as any; + }); + + const maxRetries = 2; + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await jest.runAllTimersAsync(); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + expect(response.status).toBe(500); + }); + + it("should not retry on success status codes", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback) => { + process.nextTick(callback); + return null as any; + }); + + const successStatuses = [200, 201, 202]; + + for (const status of successStatuses) { + mockFetch.mockReset(); + setTimeoutSpy.mockClear(); + mockFetch.mockResolvedValueOnce(new Response("", { status })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + await jest.runAllTimersAsync(); + await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(setTimeoutSpy).not.toHaveBeenCalled(); + } + }); + + it("should apply correct exponential backoff with jitter", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + const maxRetries = 3; + const expectedDelays = [1000, 2000, 4000]; + + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + await jest.runAllTimersAsync(); + await responsePromise; + + // Verify setTimeout calls + expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length); + + expectedDelays.forEach((delay, index) => { + expect(setTimeoutSpy).toHaveBeenNthCalledWith(index + 1, expect.any(Function), delay); + }); + + expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); + }); + + it("should handle concurrent retries independently", async () => { + setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback) => { + process.nextTick(callback); + return null as any; + }); + + mockFetch + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 200 })); + + const promise1 = requestWithRetries(() => mockFetch(), 1); + const promise2 = requestWithRetries(() => mockFetch(), 1); + + await jest.runAllTimersAsync(); + const [response1, response2] = await Promise.all([promise1, promise2]); + + expect(response1.status).toBe(200); + expect(response2.status).toBe(200); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/signals.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/signals.test.ts new file mode 100644 index 00000000000..9cabfa07447 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/signals.test.ts @@ -0,0 +1,69 @@ +import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; + +describe("Test getTimeoutSignal", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("should return an object with signal and abortId", () => { + const { signal, abortId } = getTimeoutSignal(1000); + + expect(signal).toBeDefined(); + expect(abortId).toBeDefined(); + expect(signal).toBeInstanceOf(AbortSignal); + expect(signal.aborted).toBe(false); + }); + + it("should create a signal that aborts after the specified timeout", () => { + const timeoutMs = 5000; + const { signal } = getTimeoutSignal(timeoutMs); + + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(timeoutMs - 1); + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(1); + expect(signal.aborted).toBe(true); + }); +}); + +describe("Test anySignal", () => { + it("should return an AbortSignal", () => { + const signal = anySignal(new AbortController().signal); + expect(signal).toBeInstanceOf(AbortSignal); + }); + + it("should abort when any of the input signals is aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal(controller1.signal, controller2.signal); + + expect(signal.aborted).toBe(false); + controller1.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should handle an array of signals", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal([controller1.signal, controller2.signal]); + + expect(signal.aborted).toBe(false); + controller2.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should abort immediately if one of the input signals is already aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + controller1.abort(); + + const signal = anySignal(controller1.signal, controller2.signal); + expect(signal.aborted).toBe(true); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts new file mode 100644 index 00000000000..1dc9be0cc0e --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts @@ -0,0 +1,178 @@ +import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; + +describe("Node18UniversalStreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const dest = new WritableStream({ + write(chunk) { + expect(chunk).toEqual(new TextEncoder().encode("test")); + }, + }); + + stream.pipe(dest); + }); + + it("should write to dest when calling pipe to node writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const dest = new (await import("readable-stream")).Writable({ + write(chunk, encoding, callback) { + expect(chunk.toString()).toEqual("test"); + callback(); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new WritableStream({ + write(chunk) { + buffer.push(chunk); + }, + }); + + stream.pipe(dest); + stream.unpipe(dest); + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it("should pause and resume the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + const resumeSpy = jest.spyOn(stream, "resume"); + + expect(stream.isPaused).toBe(false); + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + + expect(pauseSpy).toHaveBeenCalled(); + expect(resumeSpy).toHaveBeenCalled(); + }); + + it("should read the stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + }); + + it("should read the stream as text", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + let data = ""; + const stream = new Node18UniversalStreamWrapper(rawStream); + for await (const chunk of stream) { + data += new TextDecoder().decode(chunk); + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts new file mode 100644 index 00000000000..0c99d3b2655 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts @@ -0,0 +1,124 @@ +import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; + +describe("NodePre18StreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to node writable stream", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const dest = new (await import("readable-stream")).Writable({ + write(chunk, encoding, callback) { + expect(chunk.toString()).toEqual("test"); + callback(); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new (await import("readable-stream")).Writable({ + write(chunk, encoding, callback) { + buffer.push(chunk); + callback(); + }, + }); + stream.pipe(dest); + stream.unpipe(); + + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalledWith(); + }); + + it("should pause the stream and resume", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + expect(stream.isPaused).toBe(false); + + expect(pauseSpy).toHaveBeenCalledWith(); + }); + + it("should read the stream", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + + expect(await stream.read()).toEqual("test"); + expect(await stream.read()).toEqual("test"); + }); + + it("should read the stream as text", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = (await import("readable-stream")).Readable.from([JSON.stringify({ test: "test" })]); + const stream = new NodePre18StreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + let data = ""; + const stream = new NodePre18StreamWrapper(rawStream); + for await (const chunk of stream) { + data += chunk; + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts new file mode 100644 index 00000000000..1d171ce6c67 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts @@ -0,0 +1,153 @@ +import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; + +describe("UndiciStreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + const dest = new WritableStream({ + write(chunk) { + expect(chunk).toEqual(new TextEncoder().encode("test")); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new WritableStream({ + write(chunk) { + buffer.push(chunk); + }, + }); + stream.pipe(dest); + stream.unpipe(dest); + + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it("should pause and resume the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + const resumeSpy = jest.spyOn(stream, "resume"); + + expect(stream.isPaused).toBe(false); + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + + expect(pauseSpy).toHaveBeenCalled(); + expect(resumeSpy).toHaveBeenCalled(); + }); + + it("should read the stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + }); + + it("should read the stream as text", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + let data = ""; + const stream = new UndiciStreamWrapper(rawStream); + for await (const chunk of stream) { + data += new TextDecoder().decode(chunk); + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts new file mode 100644 index 00000000000..17cf37a2f7f --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts @@ -0,0 +1,43 @@ +import { RUNTIME } from "../../../../src/core/runtime"; +import { chooseStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; +import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; +import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; +import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; + +describe("chooseStreamWrapper", () => { + beforeEach(() => { + RUNTIME.type = "unknown"; + RUNTIME.parsedVersion = 0; + }); + + it('should return a Node18UniversalStreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is greater than or equal to 18', async () => { + const expected = new Node18UniversalStreamWrapper(new ReadableStream()); + RUNTIME.type = "node"; + RUNTIME.parsedVersion = 18; + + const result = await chooseStreamWrapper(new ReadableStream()); + + expect(JSON.stringify(result)).toBe(JSON.stringify(expected)); + }); + + it('should return a NodePre18StreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is less than 18', async () => { + const stream = await import("readable-stream"); + const expected = new NodePre18StreamWrapper(new stream.Readable()); + + RUNTIME.type = "node"; + RUNTIME.parsedVersion = 16; + + const result = await chooseStreamWrapper(new stream.Readable()); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); + }); + + it('should return a Undici when RUNTIME.type is not "node"', async () => { + const expected = new UndiciStreamWrapper(new ReadableStream()); + RUNTIME.type = "browser"; + + const result = await chooseStreamWrapper(new ReadableStream()); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/webpack.test.ts b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/webpack.test.ts new file mode 100644 index 00000000000..557db6dc4ef --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/stream-wrappers/webpack.test.ts @@ -0,0 +1,38 @@ +import webpack from "webpack"; + +describe("test env compatibility", () => { + test("webpack", () => { + return new Promise((resolve, reject) => { + webpack( + { + mode: "production", + entry: "./src/index.ts", + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [".tsx", ".ts", ".js"], + }, + }, + (err, stats) => { + try { + expect(err).toBe(null); + if (stats?.hasErrors()) { + console.log(stats?.toString()); + } + expect(stats?.hasErrors()).toBe(false); + resolve(); + } catch (error) { + reject(error); + } + } + ); + }); + }, 60_000); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/fetcher/test-file.txt b/seed/ts-sdk/path-parameters/tests/unit/fetcher/test-file.txt new file mode 100644 index 00000000000..c66d471e359 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/fetcher/test-file.txt @@ -0,0 +1 @@ +This is a test file! diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/bigint/bigint.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/bigint/bigint.test.ts new file mode 100644 index 00000000000..cf9935a749a --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/bigint/bigint.test.ts @@ -0,0 +1,24 @@ +import { bigint } from "../../../../src/core/schemas/builders/bigint"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("bigint", () => { + itSchema("converts between raw string and parsed bigint", bigint(), { + raw: "123456789012345678901234567890123456789012345678901234567890", + parsed: BigInt("123456789012345678901234567890123456789012345678901234567890"), + }); + + itValidateParse("non-string", bigint(), 42, [ + { + message: "Expected string. Received 42.", + path: [], + }, + ]); + + itValidateJson("non-bigint", bigint(), "hello", [ + { + message: 'Expected bigint. Received "hello".', + path: [], + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/date/date.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/date/date.test.ts new file mode 100644 index 00000000000..2790268a09c --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/date/date.test.ts @@ -0,0 +1,31 @@ +import { date } from "../../../../src/core/schemas/builders/date"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("date", () => { + itSchema("converts between raw ISO string and parsed Date", date(), { + raw: "2022-09-29T05:41:21.939Z", + parsed: new Date("2022-09-29T05:41:21.939Z"), + }); + + itValidateParse("non-string", date(), 42, [ + { + message: "Expected string. Received 42.", + path: [], + }, + ]); + + itValidateParse("non-ISO", date(), "hello world", [ + { + message: 'Expected ISO 8601 date string. Received "hello world".', + path: [], + }, + ]); + + itValidateJson("non-Date", date(), "hello", [ + { + message: 'Expected Date object. Received "hello".', + path: [], + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/enum/enum.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/enum/enum.test.ts new file mode 100644 index 00000000000..ab0df0285cd --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/enum/enum.test.ts @@ -0,0 +1,30 @@ +import { enum_ } from "../../../../src/core/schemas/builders/enum"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("enum", () => { + itSchemaIdentity(enum_(["A", "B", "C"]), "A"); + + itSchemaIdentity(enum_(["A", "B", "C"]), "D" as any, { + opts: { allowUnrecognizedEnumValues: true }, + }); + + itValidate("invalid enum", enum_(["A", "B", "C"]), "D", [ + { + message: 'Expected enum. Received "D".', + path: [], + }, + ]); + + itValidate( + "non-string", + enum_(["A", "B", "C"]), + [], + [ + { + message: "Expected string. Received list.", + path: [], + }, + ] + ); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/lazy.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/lazy.test.ts new file mode 100644 index 00000000000..6906bf4cf91 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/lazy.test.ts @@ -0,0 +1,57 @@ +import { Schema } from "../../../../src/core/schemas/Schema"; +import { lazy, list, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + it("doesn't run immediately", () => { + let wasRun = false; + lazy(() => { + wasRun = true; + return string(); + }); + expect(wasRun).toBe(false); + }); + + it("only runs first time", async () => { + let count = 0; + const schema = lazy(() => { + count++; + return string(); + }); + await schema.parse("hello"); + await schema.json("world"); + expect(count).toBe(1); + }); + + itSchemaIdentity( + lazy(() => object({})), + { foo: "hello" }, + { + title: "passes opts through", + opts: { unrecognizedObjectKeys: "passthrough" }, + } + ); + + itSchemaIdentity( + lazy(() => object({ foo: string() })), + { foo: "hello" } + ); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial schema doesn't compile", () => { + () => { + // @ts-expect-error + const a = lazy(() => object({ foo: a })); + }; + }); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial compiles with explicit type", () => { + () => { + interface TreeNode { + children: TreeNode[]; + } + const TreeNode: Schema = lazy(() => object({ children: list(TreeNode) })); + }; + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/lazyObject.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/lazyObject.test.ts new file mode 100644 index 00000000000..8813cc9fbb4 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/lazyObject.test.ts @@ -0,0 +1,18 @@ +import { lazyObject, number, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + itSchemaIdentity( + lazyObject(() => object({ foo: string() })), + { foo: "hello" } + ); + + itSchemaIdentity( + lazyObject(() => object({ foo: string() })).extend(object({ bar: number() })), + { + foo: "hello", + bar: 42, + }, + { title: "returned schema has object utils" } + ); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/recursive/a.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/recursive/a.ts new file mode 100644 index 00000000000..8b7d5e40cfa --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/recursive/a.ts @@ -0,0 +1,7 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { schemaB } from "./b"; + +// @ts-expect-error +export const schemaA = object({ + b: schemaB, +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/recursive/b.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/recursive/b.ts new file mode 100644 index 00000000000..fb219d54c8e --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/lazy/recursive/b.ts @@ -0,0 +1,8 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { optional } from "../../../../../src/core/schemas/builders/schema-utils"; +import { schemaA } from "./a"; + +// @ts-expect-error +export const schemaB = object({ + a: optional(schemaA), +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/list/list.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/list/list.test.ts new file mode 100644 index 00000000000..424ed642db2 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/list/list.test.ts @@ -0,0 +1,41 @@ +import { list, object, property, string } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("list", () => { + itSchemaIdentity(list(string()), ["hello", "world"], { + title: "functions as identity when item type is primitive", + }); + + itSchema( + "converts objects correctly", + list( + object({ + helloWorld: property("hello_world", string()), + }) + ), + { + raw: [{ hello_world: "123" }], + parsed: [{ helloWorld: "123" }], + } + ); + + itValidate("not a list", list(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidate( + "invalid item type", + list(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/literals/stringLiteral.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/literals/stringLiteral.test.ts new file mode 100644 index 00000000000..fa6c88873c6 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/literals/stringLiteral.test.ts @@ -0,0 +1,21 @@ +import { stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("stringLiteral", () => { + itSchemaIdentity(stringLiteral("A"), "A"); + + itValidate("incorrect string", stringLiteral("A"), "B", [ + { + path: [], + message: 'Expected "A". Received "B".', + }, + ]); + + itValidate("non-string", stringLiteral("A"), 42, [ + { + path: [], + message: 'Expected "A". Received 42.', + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/object-like/withParsedProperties.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/object-like/withParsedProperties.test.ts new file mode 100644 index 00000000000..9f5dd0ed39b --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/object-like/withParsedProperties.test.ts @@ -0,0 +1,57 @@ +import { object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; + +describe("withParsedProperties", () => { + it("Added properties included on parsed object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + printHelloWorld: () => () => "Hello world", + helloWorld: "Hello world", + }); + + const parsed = await schema.parse({ raw_foo: "value of foo", bar: "bar" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printFoo()).toBe("value of foo"); + expect(parsed.value.printHelloWorld()).toBe("Hello world"); + expect(parsed.value.helloWorld).toBe("Hello world"); + }); + + it("Added property is removed on raw object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + }); + + const original = { raw_foo: "value of foo", bar: "bar" } as const; + const parsed = await schema.parse(original); + if (!parsed.ok) { + throw new Error("Failed to parse()"); + } + + const raw = await schema.json(parsed.value); + + if (!raw.ok) { + throw new Error("Failed to json()"); + } + + expect(raw.value).toEqual(original); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .withParsedProperties(42); + }); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/object/extend.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/object/extend.test.ts new file mode 100644 index 00000000000..54fc8c4ebf8 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/object/extend.test.ts @@ -0,0 +1,89 @@ +import { boolean, object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("extend", () => { + itSchemaIdentity( + object({ + foo: string(), + }).extend( + object({ + bar: stringLiteral("bar"), + }) + ), + { + foo: "", + bar: "bar", + } as const, + { + title: "extended properties are included in schema", + } + ); + + itSchemaIdentity( + object({ + foo: string(), + }) + .extend( + object({ + bar: stringLiteral("bar"), + }) + ) + .extend( + object({ + baz: boolean(), + }) + ), + { + foo: "", + bar: "bar", + baz: true, + } as const, + { + title: "extensions can be extended", + } + ); + + itSchema( + "converts nested object", + object({ + item: object({ + helloWorld: property("hello_world", string()), + }), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item: { hello_world: "yo" }, goodbye_raw: "peace" }, + parsed: { item: { helloWorld: "yo" }, goodbye: "peace" }, + } + ); + + itSchema( + "extensions work with raw/parsed property name conversions", + object({ + item: property("item_raw", string()), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item_raw: "hi", goodbye_raw: "peace" }, + parsed: { item: "hi", goodbye: "peace" }, + } + ); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .extend([]); + }); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/object/object.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/object/object.test.ts new file mode 100644 index 00000000000..0acf0e240f6 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/object/object.test.ts @@ -0,0 +1,255 @@ +import { any, number, object, property, string, stringLiteral, unknown } from "../../../../src/core/schemas/builders"; +import { itJson, itParse, itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("object", () => { + itSchemaIdentity( + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { + foo: "", + bar: "bar", + }, + { + title: "functions as identity when values are primitives and property() isn't used", + } + ); + + itSchema( + "uses raw key from property()", + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { raw_foo: "foo", bar: "bar" }, + parsed: { foo: "foo", bar: "bar" }, + } + ); + + itSchema( + "keys with unknown type can be omitted", + object({ + foo: unknown(), + }), + { + raw: {}, + parsed: {}, + } + ); + + itSchema( + "keys with any type can be omitted", + object({ + foo: any(), + }), + { + raw: {}, + parsed: {}, + } + ); + + describe("unrecognizedObjectKeys", () => { + describe("parse", () => { + itParse( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itParse( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + + describe("json", () => { + itJson( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itJson( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + }); + + describe("nullish properties", () => { + itSchema("missing properties are not added", object({ foo: property("raw_foo", string().optional()) }), { + raw: {}, + parsed: {}, + }); + + itSchema("undefined properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + itSchema("null properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + describe("extensions", () => { + itSchema( + "undefined properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + + describe("parse()", () => { + itParse( + "null properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + }); + }); + }); + + itValidate( + "missing property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello" }, + [ + { + path: [], + message: 'Missing required key "bar"', + }, + ] + ); + + itValidate( + "extra property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello", bar: "bar", baz: 42 }, + [ + { + path: ["baz"], + message: 'Unexpected key "baz"', + }, + ] + ); + + itValidate( + "not an object", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "nested validation error", + object({ + foo: object({ + bar: number(), + }), + }), + { foo: { bar: "hello" } }, + [ + { + path: ["foo", "bar"], + message: 'Expected number. Received "hello".', + }, + ] + ); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts new file mode 100644 index 00000000000..d87a65febfd --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts @@ -0,0 +1,21 @@ +import { objectWithoutOptionalProperties, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("objectWithoutOptionalProperties", () => { + itSchema( + "all properties are required", + objectWithoutOptionalProperties({ + foo: string(), + bar: stringLiteral("bar").optional(), + }), + { + raw: { + foo: "hello", + }, + // @ts-expect-error + parsed: { + foo: "hello", + }, + } + ); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/any.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/any.test.ts new file mode 100644 index 00000000000..1adbbe2a838 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/any.test.ts @@ -0,0 +1,6 @@ +import { any } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("any", () => { + itSchemaIdentity(any(), true); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/boolean.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/boolean.test.ts new file mode 100644 index 00000000000..897a8295dca --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/boolean.test.ts @@ -0,0 +1,14 @@ +import { boolean } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("boolean", () => { + itSchemaIdentity(boolean(), true); + + itValidate("non-boolean", boolean(), {}, [ + { + path: [], + message: "Expected boolean. Received object.", + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/number.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/number.test.ts new file mode 100644 index 00000000000..2d01415a60b --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/number.test.ts @@ -0,0 +1,14 @@ +import { number } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("number", () => { + itSchemaIdentity(number(), 42); + + itValidate("non-number", number(), "hello", [ + { + path: [], + message: 'Expected number. Received "hello".', + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/string.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/string.test.ts new file mode 100644 index 00000000000..57b2368784a --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/string.test.ts @@ -0,0 +1,14 @@ +import { string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("string", () => { + itSchemaIdentity(string(), "hello"); + + itValidate("non-string", string(), 42, [ + { + path: [], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/unknown.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/unknown.test.ts new file mode 100644 index 00000000000..4d17a7dbd00 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/primitives/unknown.test.ts @@ -0,0 +1,6 @@ +import { unknown } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("unknown", () => { + itSchemaIdentity(unknown(), true); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/record/record.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/record/record.test.ts new file mode 100644 index 00000000000..7e4ba39cc55 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/record/record.test.ts @@ -0,0 +1,34 @@ +import { number, record, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("record", () => { + itSchemaIdentity(record(string(), string()), { hello: "world" }); + itSchemaIdentity(record(number(), string()), { 42: "world" }); + + itValidate( + "non-record", + record(number(), string()), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate("invalid key type", record(number(), string()), { hello: "world" }, [ + { + path: ["hello (key)"], + message: 'Expected number. Received "hello".', + }, + ]); + + itValidate("invalid value type", record(string(), number()), { hello: "world" }, [ + { + path: ["hello"], + message: 'Expected number. Received "world".', + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts new file mode 100644 index 00000000000..da10086bc1d --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts @@ -0,0 +1,83 @@ +import { object, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("getSchemaUtils", () => { + describe("optional()", () => { + itSchema("optional fields allow original schema", string().optional(), { + raw: "hello", + parsed: "hello", + }); + + itSchema("optional fields are not required", string().optional(), { + raw: null, + parsed: undefined, + }); + }); + + describe("transform()", () => { + itSchema( + "transorm and untransform run correctly", + string().transform({ + transform: (x) => x + "X", + untransform: (x) => (x as string).slice(0, -1), + }), + { + raw: "hello", + parsed: "helloX", + } + ); + }); + + describe("parseOrThrow()", () => { + it("parses valid value", async () => { + const value = string().parseOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).parseOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("jsonOrThrow()", () => { + it("serializes valid value", async () => { + const value = string().jsonOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).jsonOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("omitUndefined", () => { + it("serializes undefined as null", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow({ + a: "hello", + b: undefined, + }); + expect(value).toEqual({ a: "hello", b: null }); + }); + + it("omits undefined values", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow( + { + a: "hello", + b: undefined, + }, + { + omitUndefined: true, + } + ); + expect(value).toEqual({ a: "hello" }); + }); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/schema.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/schema.test.ts new file mode 100644 index 00000000000..94089a9a91b --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/schema.test.ts @@ -0,0 +1,78 @@ +import { + boolean, + discriminant, + list, + number, + object, + string, + stringLiteral, + union, +} from "../../../src/core/schemas/builders"; +import { booleanLiteral } from "../../../src/core/schemas/builders/literals/booleanLiteral"; +import { property } from "../../../src/core/schemas/builders/object/property"; +import { itSchema } from "./utils/itSchema"; + +describe("Schema", () => { + itSchema( + "large nested object", + object({ + a: string(), + b: stringLiteral("b value"), + c: property( + "raw_c", + list( + object({ + animal: union(discriminant("type", "_type"), { + dog: object({ value: boolean() }), + cat: object({ value: property("raw_cat", number()) }), + }), + }) + ) + ), + d: property("raw_d", boolean()), + e: booleanLiteral(true), + }), + { + raw: { + a: "hello", + b: "b value", + raw_c: [ + { + animal: { + _type: "dog", + value: true, + }, + }, + { + animal: { + _type: "cat", + raw_cat: 42, + }, + }, + ], + raw_d: false, + e: true, + }, + parsed: { + a: "hello", + b: "b value", + c: [ + { + animal: { + type: "dog", + value: true, + }, + }, + { + animal: { + type: "cat", + value: 42, + }, + }, + ], + d: false, + e: true, + }, + } + ); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/set/set.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/set/set.test.ts new file mode 100644 index 00000000000..e17f908c80e --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/set/set.test.ts @@ -0,0 +1,48 @@ +import { set, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("set", () => { + itSchema("converts between raw list and parsed Set", set(string()), { + raw: ["A", "B"], + parsed: new Set(["A", "B"]), + }); + + itValidateParse("not a list", set(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidateJson( + "not a Set", + set(string()), + [], + [ + { + path: [], + message: "Expected Set. Received list.", + }, + ] + ); + + itValidateParse( + "invalid item type", + set(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); + + itValidateJson("invalid item type", set(string()), new Set([42]), [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/skipValidation.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/skipValidation.test.ts new file mode 100644 index 00000000000..5dc88096a9f --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/skipValidation.test.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-console */ + +import { boolean, number, object, property, string, undiscriminatedUnion } from "../../../src/core/schemas/builders"; + +describe("skipValidation", () => { + it("allows data that doesn't conform to the schema", async () => { + const warningLogs: string[] = []; + const originalConsoleWarn = console.warn; + console.warn = (...args) => warningLogs.push(args.join(" ")); + + const schema = object({ + camelCase: property("snake_case", string()), + numberProperty: number(), + requiredProperty: boolean(), + anyPrimitive: undiscriminatedUnion([string(), number(), boolean()]), + }); + + const parsed = await schema.parse( + { + snake_case: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + { + skipValidation: true, + } + ); + + expect(parsed).toEqual({ + ok: true, + value: { + camelCase: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + }); + + expect(warningLogs).toEqual([ + `Failed to validate. + - numberProperty: Expected number. Received "oops".`, + ]); + + console.warn = originalConsoleWarn; + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts new file mode 100644 index 00000000000..0e66433371c --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts @@ -0,0 +1,44 @@ +import { number, object, property, string, undiscriminatedUnion } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("undiscriminatedUnion", () => { + itSchemaIdentity(undiscriminatedUnion([string(), number()]), "hello world"); + + itSchemaIdentity(undiscriminatedUnion([object({ hello: string() }), object({ goodbye: string() })]), { + goodbye: "foo", + }); + + itSchema( + "Correctly transforms", + undiscriminatedUnion([object({ hello: string() }), object({ helloWorld: property("hello_world", string()) })]), + { + raw: { hello_world: "foo " }, + parsed: { helloWorld: "foo " }, + } + ); + + it("Returns errors for all variants", async () => { + const result = await undiscriminatedUnion([string(), number()]).parse(true); + if (result.ok) { + throw new Error("Unexpectedly passed validation"); + } + expect(result.errors).toEqual([ + { + message: "[Variant 0] Expected string. Received true.", + path: [], + }, + { + message: "[Variant 1] Expected number. Received true.", + path: [], + }, + ]); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with zero members", () => { + // @ts-expect-error + () => undiscriminatedUnion([]); + }); + }); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/union/union.test.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/union/union.test.ts new file mode 100644 index 00000000000..790184603ac --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/union/union.test.ts @@ -0,0 +1,113 @@ +import { boolean, discriminant, number, object, string, union } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("union", () => { + itSchemaIdentity( + union("type", { + lion: object({ + meows: boolean(), + }), + giraffe: object({ + heightInInches: number(), + }), + }), + { type: "lion", meows: true }, + { title: "doesn't transform discriminant when it's a string" } + ); + + itSchema( + "transforms discriminant when it's a discriminant()", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + raw: { _type: "lion", meows: true }, + parsed: { type: "lion", meows: true }, + } + ); + + describe("allowUnrecognizedUnionMembers", () => { + itSchema( + "transforms discriminant & passes through values when discriminant value is unrecognized", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + // @ts-expect-error + raw: { _type: "moose", isAMoose: true }, + // @ts-expect-error + parsed: { type: "moose", isAMoose: true }, + opts: { + allowUnrecognizedUnionMembers: true, + }, + } + ); + }); + + describe("withParsedProperties", () => { + it("Added property is included on parsed object", async () => { + const schema = union("type", { + lion: object({}), + tiger: object({ value: string() }), + }).withParsedProperties({ + printType: (parsed) => () => parsed.type, + }); + + const parsed = await schema.parse({ type: "lion" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printType()).toBe("lion"); + }); + }); + + itValidate( + "non-object", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "missing discriminant", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + {}, + [ + { + path: [], + message: 'Missing discriminant ("type")', + }, + ] + ); + + itValidate( + "unrecognized discriminant value", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + { + type: "bear", + }, + [ + { + path: ["type"], + message: 'Expected enum. Received "bear".', + }, + ] + ); +}); diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/utils/itSchema.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/utils/itSchema.ts new file mode 100644 index 00000000000..67b6c928175 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/utils/itSchema.ts @@ -0,0 +1,78 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions } from "../../../../src/core/schemas/Schema"; + +export function itSchemaIdentity( + schema: Schema, + value: T, + { title = "functions as identity", opts }: { title?: string; opts?: SchemaOptions } = {} +): void { + itSchema(title, schema, { raw: value, parsed: value, opts }); +} + +export function itSchema( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + only = false, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + only?: boolean; + } +): void { + // eslint-disable-next-line jest/valid-title + (only ? describe.only : describe)(title, () => { + itParse("parse()", schema, { raw, parsed, opts }); + itJson("json()", schema, { raw, parsed, opts }); + }); +} + +export function itParse( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.parse(raw, opts); + if (!maybeValid.ok) { + throw new Error("Failed to parse() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(parsed); + }); +} + +export function itJson( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.json(parsed, opts); + if (!maybeValid.ok) { + throw new Error("Failed to json() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(raw); + }); +} diff --git a/seed/ts-sdk/path-parameters/tests/unit/zurg/utils/itValidate.ts b/seed/ts-sdk/path-parameters/tests/unit/zurg/utils/itValidate.ts new file mode 100644 index 00000000000..75b2c08b036 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tests/unit/zurg/utils/itValidate.ts @@ -0,0 +1,56 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions, ValidationError } from "../../../../src/core/schemas/Schema"; + +export function itValidate( + title: string, + schema: Schema, + input: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + // eslint-disable-next-line jest/valid-title + describe("parse()", () => { + itValidateParse(title, schema, input, errors, opts); + }); + describe("json()", () => { + itValidateJson(title, schema, input, errors, opts); + }); +} + +export function itValidateParse( + title: string, + schema: Schema, + raw: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("parse", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.parse(raw, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +} + +export function itValidateJson( + title: string, + schema: Schema, + parsed: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("json", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.json(parsed, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +} diff --git a/seed/ts-sdk/path-parameters/tsconfig.json b/seed/ts-sdk/path-parameters/tsconfig.json new file mode 100644 index 00000000000..538c94fe015 --- /dev/null +++ b/seed/ts-sdk/path-parameters/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "extendedDiagnostics": true, + "strict": true, + "target": "ES6", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "baseUrl": "src" + }, + "include": ["src"], + "exclude": [] +} diff --git a/test-definitions/fern/apis/path-parameters/definition/api.yml b/test-definitions/fern/apis/path-parameters/definition/api.yml new file mode 100644 index 00000000000..fd4112ac896 --- /dev/null +++ b/test-definitions/fern/apis/path-parameters/definition/api.yml @@ -0,0 +1 @@ +name: path-parameters diff --git a/test-definitions/fern/apis/path-parameters/definition/user.yml b/test-definitions/fern/apis/path-parameters/definition/user.yml new file mode 100644 index 00000000000..4f1597393d7 --- /dev/null +++ b/test-definitions/fern/apis/path-parameters/definition/user.yml @@ -0,0 +1,35 @@ +types: + User: + properties: + name: string + tags: list + +service: + base-path: /user + auth: false + endpoints: + getOrganization: + path: "/organizations/{organizationId}" + method: GET + path-parameters: + organizationId: string + response: User + + getUser: + path: "/users/{userId}" + method: GET + request: + name: GetUsersRequest + path-parameters: + userId: string + response: User + + getOrganizationUser: + path: "/organizations/{organizationId}/users/{userId}" + method: GET + request: + name: GetOrganizationUserRequest + path-parameters: + organizationId: string + userId: string + response: User diff --git a/test-definitions/fern/apis/path-parameters/generators.yml b/test-definitions/fern/apis/path-parameters/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/test-definitions/fern/apis/path-parameters/generators.yml @@ -0,0 +1 @@ +{}