diff --git a/packages/commons/docs-loader/src/readonly-docs-loader.ts b/packages/commons/docs-loader/src/readonly-docs-loader.ts index 653b50d706..f17a24b3a7 100644 --- a/packages/commons/docs-loader/src/readonly-docs-loader.ts +++ b/packages/commons/docs-loader/src/readonly-docs-loader.ts @@ -475,7 +475,14 @@ const createGetPrunedApiCached = (domainKey: string, cacheConfig: Required) => defaultSearchFilters: settings?.defaultSearchFilters ?? false, disableSearch: settings?.disableSearch ?? false, hide404Page: settings?.hide404Page ?? false, - httpSnippets: settings?.httpSnippets ?? false, + httpSnippets: settings?.httpSnippets ?? undefined, searchText: settings?.searchText ?? "Search" }; }); diff --git a/packages/commons/docs-server/src/auth/getAuthState.ts b/packages/commons/docs-server/src/auth/getAuthState.ts index 478122eaf3..f3ee6cc001 100644 --- a/packages/commons/docs-server/src/auth/getAuthState.ts +++ b/packages/commons/docs-server/src/auth/getAuthState.ts @@ -9,7 +9,6 @@ import { type PreviewUrlAuth, getAuthEdgeConfig, getPreviewUrlAuthConfig } from import { isLocal } from "../isLocal"; import { isSelfHosted } from "../isSelfHosted"; -import { safeVerifyFernJWTConfig } from "./FernJWT"; import { getAllowedRedirectUrls } from "./allowed-redirects"; import { getOAuth2AuthorizationUrl } from "./oauth2"; import { preferPreview } from "./origin"; @@ -18,6 +17,7 @@ import { getReturnToQueryParam } from "./return-to"; import { getWebflowAuthorizationUrl } from "./webflow"; import { getWorkosSSOAuthorizationUrl } from "./workos"; import { handleWorkosAuth } from "./workos-handler"; +import { safeVerifyFernJWTConfig } from "./FernJWT"; export type AuthPartner = "workos" | "ory" | "webflow" | "custom" | string; diff --git a/packages/fdr-sdk/src/api-definition/snippets/__snapshots__/backfill.test.ts.snap b/packages/fdr-sdk/src/api-definition/snippets/__snapshots__/backfill.test.ts.snap index 651f9d708d..adbb3a2a6a 100644 --- a/packages/fdr-sdk/src/api-definition/snippets/__snapshots__/backfill.test.ts.snap +++ b/packages/fdr-sdk/src/api-definition/snippets/__snapshots__/backfill.test.ts.snap @@ -1627,6 +1627,127 @@ dataTask.resume()", } `; +exports[`backfillSnippets > should backfill snippets with just ruby http snippets 1`] = ` +{ + "ruby": [ + { + "code": "require 'uri' +require 'net/http' + +url = URI("https://api.example.com/v1/") + +http = Net::HTTP.new(url.host, url.port) +http.use_ssl = true + +request = Net::HTTP::Post.new(url) +request["Content-Type"] = 'application/json' +request.body = "{\\n \\"RecordCount\\": 50,\\n \\"DocumentSearchParams\\": {\\n \\"SearchTerms\\": {\\n \\"All\\": [\\n \\"data security\\"\\n ],\\n \\"Any\\": [\\n \\"cyberattack\\",\\n \\"breach\\"\\n ],\\n \\"None\\": [\\n \\"ransomware\\"\\n ]\\n },\\n \\"DocumentDateRangeStart\\": \\"2023-01-01T00:00:00Z\\",\\n \\"DocumentDateRangeEnd\\": \\"2023-12-31T23:59:59Z\\"\\n }\\n}" + +response = http.request(request) +puts response.read_body", + "description": undefined, + "generated": true, + "install": undefined, + "language": "ruby", + "name": undefined, + }, + ], +} +`; + +exports[`backfillSnippets > should backfill snippets with no http snippets 1`] = ` +{ + "curl": [ + { + "code": "curl -X POST https://api.example.com/v1/ \\ + -H "Content-Type: application/json" \\ + -d '{ + "RecordCount": 50, + "DocumentSearchParams": { + "SearchTerms": { + "All": [ + "data security" + ], + "Any": [ + "cyberattack", + "breach" + ], + "None": [ + "ransomware" + ] + }, + "DocumentDateRangeStart": "2023-01-01T00:00:00Z", + "DocumentDateRangeEnd": "2023-12-31T23:59:59Z" + } +}'", + "description": undefined, + "generated": true, + "install": undefined, + "language": "curl", + "name": undefined, + }, + ], +} +`; + +exports[`backfillSnippets > should backfill snippets with ruby and curl http snippets 1`] = ` +{ + "curl": [ + { + "code": "curl -X POST https://api.example.com/v1/ \\ + -H "Content-Type: application/json" \\ + -d '{ + "RecordCount": 50, + "DocumentSearchParams": { + "SearchTerms": { + "All": [ + "data security" + ], + "Any": [ + "cyberattack", + "breach" + ], + "None": [ + "ransomware" + ] + }, + "DocumentDateRangeStart": "2023-01-01T00:00:00Z", + "DocumentDateRangeEnd": "2023-12-31T23:59:59Z" + } +}'", + "description": undefined, + "generated": true, + "install": undefined, + "language": "curl", + "name": undefined, + }, + ], + "ruby": [ + { + "code": "require 'uri' +require 'net/http' + +url = URI("https://api.example.com/v1/") + +http = Net::HTTP.new(url.host, url.port) +http.use_ssl = true + +request = Net::HTTP::Post.new(url) +request["Content-Type"] = 'application/json' +request.body = "{\\n \\"RecordCount\\": 50,\\n \\"DocumentSearchParams\\": {\\n \\"SearchTerms\\": {\\n \\"All\\": [\\n \\"data security\\"\\n ],\\n \\"Any\\": [\\n \\"cyberattack\\",\\n \\"breach\\"\\n ],\\n \\"None\\": [\\n \\"ransomware\\"\\n ]\\n },\\n \\"DocumentDateRangeStart\\": \\"2023-01-01T00:00:00Z\\",\\n \\"DocumentDateRangeEnd\\": \\"2023-12-31T23:59:59Z\\"\\n }\\n}" + +response = http.request(request) +puts response.read_body", + "description": undefined, + "generated": true, + "install": undefined, + "language": "ruby", + "name": undefined, + }, + ], +} +`; + exports[`backfillSnippets > should skip head method for dynamic snippets 1`] = ` { "csharp": [ diff --git a/packages/fdr-sdk/src/api-definition/snippets/backfill.test.ts b/packages/fdr-sdk/src/api-definition/snippets/backfill.test.ts index 3d0e2f778d..3f886db0cb 100644 --- a/packages/fdr-sdk/src/api-definition/snippets/backfill.test.ts +++ b/packages/fdr-sdk/src/api-definition/snippets/backfill.test.ts @@ -115,12 +115,15 @@ describe("backfillSnippets", () => { snippetsConfiguration: undefined }; - const flags = { - isHttpSnippetsEnabled: true, - alwaysEnableJavaScriptFetch: true - }; + const isHttpSnippetsEnabled = true; + const alwaysEnableJavaScriptFetch = true; - const result = await backfillSnippets(apiDefinition, undefined, flags); + const result = await backfillSnippets({ + apiDefinition, + dynamicIr: undefined, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); // Verify the result has the expected structure const endpoint = result.endpoints[EndpointId("search")]; @@ -223,16 +226,19 @@ describe("backfillSnippets", () => { snippetsConfiguration: undefined }; - const flags = { - isHttpSnippetsEnabled: true, - alwaysEnableJavaScriptFetch: true - }; + const isHttpSnippetsEnabled = true; + const alwaysEnableJavaScriptFetch = true; const dynamicIr = { typescript: typescriptFixture }; - const result = await backfillSnippets(apiDefinition, dynamicIr, flags); + const result = await backfillSnippets({ + apiDefinition, + dynamicIr, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); // Verify the result has the expected structure const endpoint = result.endpoints[EndpointId("createUser")]; @@ -375,16 +381,19 @@ describe("backfillSnippets", () => { snippetsConfiguration: undefined }; - const flags = { - isHttpSnippetsEnabled: true, - alwaysEnableJavaScriptFetch: true - }; + const isHttpSnippetsEnabled = true; + const alwaysEnableJavaScriptFetch = true; const dynamicIr = { typescript: typescriptFixture }; - const result = await backfillSnippets(apiDefinition, dynamicIr, flags); + const result = await backfillSnippets({ + apiDefinition, + dynamicIr, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); // Verify the result has the expected structure const endpoint = result.endpoints[EndpointId("createUser")]; @@ -582,16 +591,19 @@ describe("backfillSnippets", () => { snippetsConfiguration: undefined }; - const flags = { - isHttpSnippetsEnabled: true, - alwaysEnableJavaScriptFetch: true - }; + const isHttpSnippetsEnabled = true; + const alwaysEnableJavaScriptFetch = true; const dynamicIr = { typescript: typescriptFixture }; - const result = await backfillSnippets(apiDefinition, dynamicIr, flags); + const result = await backfillSnippets({ + apiDefinition, + dynamicIr, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); // Verify the result has the expected structure const endpoint = result.endpoints[EndpointId("createUser")]; @@ -713,17 +725,20 @@ describe("backfillSnippets", () => { snippetsConfiguration: undefined }; - const flags = { - isHttpSnippetsEnabled: true, - alwaysEnableJavaScriptFetch: true - }; + const isHttpSnippetsEnabled = true; + const alwaysEnableJavaScriptFetch = true; const dynamicIr = { typescript: typescriptFixture, python: pythonFixture }; - const result = await backfillSnippets(apiDefinition, dynamicIr, flags); + const result = await backfillSnippets({ + apiDefinition, + dynamicIr, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); // Verify the result has the expected structure const endpoint = result.endpoints[EndpointId("createUser")]; @@ -837,10 +852,8 @@ describe("backfillSnippets", () => { snippetsConfiguration: undefined }; - const flags = { - isHttpSnippetsEnabled: true, - alwaysEnableJavaScriptFetch: true - }; + const isHttpSnippetsEnabled = true; + const alwaysEnableJavaScriptFetch = true; const dynamicIr = { typescript: typescriptFixture, @@ -852,7 +865,12 @@ describe("backfillSnippets", () => { csharp: csharpFixture }; - const result = await backfillSnippets(apiDefinition, dynamicIr, flags); + const result = await backfillSnippets({ + apiDefinition, + dynamicIr, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); // Verify the result has the expected structure const endpoint = result.endpoints[EndpointId("createUser")]; @@ -958,10 +976,8 @@ describe("backfillSnippets", () => { snippetsConfiguration: undefined }; - const flags = { - isHttpSnippetsEnabled: true, - alwaysEnableJavaScriptFetch: true - }; + const isHttpSnippetsEnabled = true; + const alwaysEnableJavaScriptFetch = true; const dynamicIr = { typescript: typescriptFixture, @@ -973,7 +989,12 @@ describe("backfillSnippets", () => { csharp: csharpFixture }; - const result = await backfillSnippets(apiDefinition, dynamicIr, flags); + const result = await backfillSnippets({ + apiDefinition, + dynamicIr, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); // Verify the result has the expected structure const endpoint = result.endpoints[EndpointId("checkUserExists")]; @@ -1015,4 +1036,418 @@ describe("backfillSnippets", () => { expect(example?.snippets).toMatchSnapshot(); }); + + it("should backfill snippets with no http snippets", async () => { + const apiDefinition: ApiDefinition = { + id: ApiDefinitionId("test-api"), + endpoints: { + [EndpointId("search")]: { + id: EndpointId("search"), + method: "POST", + path: [{ type: "literal", value: "/" }], + displayName: undefined, + operationId: undefined, + auth: undefined, + defaultEnvironment: undefined, + environments: [ + { + id: EnvironmentId("default"), + baseUrl: "https://api.example.com/v1" + } + ], + pathParameters: undefined, + queryParameters: undefined, + requestHeaders: undefined, + responseHeaders: undefined, + requests: undefined, + responses: undefined, + errors: undefined, + snippetTemplates: undefined, + protocol: undefined, + description: undefined, + availability: undefined, + namespace: undefined, + examples: [ + { + name: "Basic Search", + description: "", + path: "/", + pathParameters: {}, + queryParameters: {}, + headers: {}, + requestBody: { + type: "json", + value: { + RecordCount: 50, + DocumentSearchParams: { + SearchTerms: { + All: ["data security"], + Any: ["cyberattack", "breach"], + None: ["ransomware"] + }, + DocumentDateRangeStart: "2023-01-01T00:00:00Z", + DocumentDateRangeEnd: "2023-12-31T23:59:59Z" + } + } + }, + responseStatusCode: 200, + responseBody: { + type: "json", + value: { + TotalDocumentCount: 833494, + NotReturnedDocumentCount: 0, + ReturnedDocumentCount: 2, + TruncatedDocumentCount: 0, + Documents: [ + { + IsTruncated: false, + DocumentID: 77187402516, + DocumentType: "NEWS", + UploadDate: "2024-10-22T16:54:36Z", + DocumentDate: "2024-10-22T15:17:54Z", + DocumentName: "Wall Street Continues to See Recent Rally Cooling", + DocumentText: "U.S. stocks dropped again on Tuesday...", + AuthorName: "Natalie Venegas", + DocumentSourceURL: + "https://www.newsweek.com/wall-street-continues-see-recent-rally-cooling-1972948", + DocumentImageURL: + "https://d.newsweek.com/en/full/2501402/new-york-stock-exchange.jpg", + LanguageID: "en", + DocumentSentimentScore: "-13", + ContainsViolence: true + } + ], + FavIcons: { + NEWS: "https://d1hgo075dbsz4i.cloudfront.net/21f4f43585ea45aa539034866e692e21/a/News/dsicon" + } + } + }, + snippets: undefined + } + ] + } + }, + auths: {}, + websockets: {}, + webhooks: {}, + types: {}, + globalHeaders: [], + subpackages: {}, + snippetsConfiguration: undefined + }; + + const isHttpSnippetsEnabled = false; + const alwaysEnableJavaScriptFetch = true; + + const result = await backfillSnippets({ + apiDefinition, + dynamicIr: undefined, + httpSnippets: isHttpSnippetsEnabled, + alwaysEnableJavaScriptFetch + }); + + // Verify the result has the expected structure + const endpoint = result.endpoints[EndpointId("search")]; + expect(endpoint).toBeDefined(); + const examples = endpoint?.examples; + expect(examples).toHaveLength(1); + + const example = examples?.[0]; + expect(example).toBeDefined(); + + // Verify snippets were generated + const snippets = example?.snippets; + expect(snippets).toBeDefined(); + const curlSnippets = snippets?.curl; + expect(curlSnippets).toBeDefined(); + expect(curlSnippets).toHaveLength(1); + + const typescriptSnippets = snippets?.typescript; + expect(typescriptSnippets).toBeUndefined(); + + // Verify curl snippet contains expected content + const curlSnippet = curlSnippets?.[0]; + expect(curlSnippet?.language).toBe("curl"); + expect(curlSnippet?.code).toContain("curl"); + expect(curlSnippet?.code).toContain("POST"); + expect(curlSnippet?.code).toContain('"RecordCount": 50'); + expect(curlSnippet?.code).toContain('"data security"'); + + expect(example?.snippets).toMatchSnapshot(); + }); + + it("should backfill snippets with ruby and curl http snippets", async () => { + const apiDefinition: ApiDefinition = { + id: ApiDefinitionId("test-api"), + endpoints: { + [EndpointId("search")]: { + id: EndpointId("search"), + method: "POST", + path: [{ type: "literal", value: "/" }], + displayName: undefined, + operationId: undefined, + auth: undefined, + defaultEnvironment: undefined, + environments: [ + { + id: EnvironmentId("default"), + baseUrl: "https://api.example.com/v1" + } + ], + pathParameters: undefined, + queryParameters: undefined, + requestHeaders: undefined, + responseHeaders: undefined, + requests: undefined, + responses: undefined, + errors: undefined, + snippetTemplates: undefined, + protocol: undefined, + description: undefined, + availability: undefined, + namespace: undefined, + examples: [ + { + name: "Basic Search", + description: "", + path: "/", + pathParameters: {}, + queryParameters: {}, + headers: {}, + requestBody: { + type: "json", + value: { + RecordCount: 50, + DocumentSearchParams: { + SearchTerms: { + All: ["data security"], + Any: ["cyberattack", "breach"], + None: ["ransomware"] + }, + DocumentDateRangeStart: "2023-01-01T00:00:00Z", + DocumentDateRangeEnd: "2023-12-31T23:59:59Z" + } + } + }, + responseStatusCode: 200, + responseBody: { + type: "json", + value: { + TotalDocumentCount: 833494, + NotReturnedDocumentCount: 0, + ReturnedDocumentCount: 2, + TruncatedDocumentCount: 0, + Documents: [ + { + IsTruncated: false, + DocumentID: 77187402516, + DocumentType: "NEWS", + UploadDate: "2024-10-22T16:54:36Z", + DocumentDate: "2024-10-22T15:17:54Z", + DocumentName: "Wall Street Continues to See Recent Rally Cooling", + DocumentText: "U.S. stocks dropped again on Tuesday...", + AuthorName: "Natalie Venegas", + DocumentSourceURL: + "https://www.newsweek.com/wall-street-continues-see-recent-rally-cooling-1972948", + DocumentImageURL: + "https://d.newsweek.com/en/full/2501402/new-york-stock-exchange.jpg", + LanguageID: "en", + DocumentSentimentScore: "-13", + ContainsViolence: true + } + ], + FavIcons: { + NEWS: "https://d1hgo075dbsz4i.cloudfront.net/21f4f43585ea45aa539034866e692e21/a/News/dsicon" + } + } + }, + snippets: undefined + } + ] + } + }, + auths: {}, + websockets: {}, + webhooks: {}, + types: {}, + globalHeaders: [], + subpackages: {}, + snippetsConfiguration: undefined + }; + + const alwaysEnableJavaScriptFetch = true; + + const result = await backfillSnippets({ + apiDefinition, + dynamicIr: undefined, + httpSnippets: ["curl", "ruby"], + alwaysEnableJavaScriptFetch + }); + + // Verify the result has the expected structure + const endpoint = result.endpoints[EndpointId("search")]; + expect(endpoint).toBeDefined(); + const examples = endpoint?.examples; + expect(examples).toHaveLength(1); + + const example = examples?.[0]; + expect(example).toBeDefined(); + + // Verify snippets were generated + const snippets = example?.snippets; + expect(snippets).toBeDefined(); + const curlSnippets = snippets?.curl; + expect(curlSnippets).toBeDefined(); + expect(curlSnippets).toHaveLength(1); + + const typescriptSnippets = snippets?.typescript; + expect(typescriptSnippets).toBeUndefined(); + + const rubySnippets = snippets?.ruby; + expect(rubySnippets).toBeDefined(); + expect(rubySnippets).toHaveLength(1); + + // Verify curl snippet contains expected content + const curlSnippet = curlSnippets?.[0]; + expect(curlSnippet?.language).toBe("curl"); + expect(curlSnippet?.code).toContain("curl"); + expect(curlSnippet?.code).toContain("POST"); + expect(curlSnippet?.code).toContain('"RecordCount": 50'); + expect(curlSnippet?.code).toContain('"data security"'); + + expect(example?.snippets).toMatchSnapshot(); + }); + + it("should backfill snippets with just ruby http snippets", async () => { + const apiDefinition: ApiDefinition = { + id: ApiDefinitionId("test-api"), + endpoints: { + [EndpointId("search")]: { + id: EndpointId("search"), + method: "POST", + path: [{ type: "literal", value: "/" }], + displayName: undefined, + operationId: undefined, + auth: undefined, + defaultEnvironment: undefined, + environments: [ + { + id: EnvironmentId("default"), + baseUrl: "https://api.example.com/v1" + } + ], + pathParameters: undefined, + queryParameters: undefined, + requestHeaders: undefined, + responseHeaders: undefined, + requests: undefined, + responses: undefined, + errors: undefined, + snippetTemplates: undefined, + protocol: undefined, + description: undefined, + availability: undefined, + namespace: undefined, + examples: [ + { + name: "Basic Search", + description: "", + path: "/", + pathParameters: {}, + queryParameters: {}, + headers: {}, + requestBody: { + type: "json", + value: { + RecordCount: 50, + DocumentSearchParams: { + SearchTerms: { + All: ["data security"], + Any: ["cyberattack", "breach"], + None: ["ransomware"] + }, + DocumentDateRangeStart: "2023-01-01T00:00:00Z", + DocumentDateRangeEnd: "2023-12-31T23:59:59Z" + } + } + }, + responseStatusCode: 200, + responseBody: { + type: "json", + value: { + TotalDocumentCount: 833494, + NotReturnedDocumentCount: 0, + ReturnedDocumentCount: 2, + TruncatedDocumentCount: 0, + Documents: [ + { + IsTruncated: false, + DocumentID: 77187402516, + DocumentType: "NEWS", + UploadDate: "2024-10-22T16:54:36Z", + DocumentDate: "2024-10-22T15:17:54Z", + DocumentName: "Wall Street Continues to See Recent Rally Cooling", + DocumentText: "U.S. stocks dropped again on Tuesday...", + AuthorName: "Natalie Venegas", + DocumentSourceURL: + "https://www.newsweek.com/wall-street-continues-see-recent-rally-cooling-1972948", + DocumentImageURL: + "https://d.newsweek.com/en/full/2501402/new-york-stock-exchange.jpg", + LanguageID: "en", + DocumentSentimentScore: "-13", + ContainsViolence: true + } + ], + FavIcons: { + NEWS: "https://d1hgo075dbsz4i.cloudfront.net/21f4f43585ea45aa539034866e692e21/a/News/dsicon" + } + } + }, + snippets: undefined + } + ] + } + }, + auths: {}, + websockets: {}, + webhooks: {}, + types: {}, + globalHeaders: [], + subpackages: {}, + snippetsConfiguration: undefined + }; + + const alwaysEnableJavaScriptFetch = true; + + const result = await backfillSnippets({ + apiDefinition, + dynamicIr: undefined, + httpSnippets: ["ruby"], + alwaysEnableJavaScriptFetch + }); + + // Verify the result has the expected structure + const endpoint = result.endpoints[EndpointId("search")]; + expect(endpoint).toBeDefined(); + const examples = endpoint?.examples; + expect(examples).toHaveLength(1); + + const example = examples?.[0]; + expect(example).toBeDefined(); + + // Verify snippets were generated + const snippets = example?.snippets; + expect(snippets).toBeDefined(); + const curlSnippets = snippets?.curl; + expect(curlSnippets).toBeUndefined(); + + const typescriptSnippets = snippets?.typescript; + expect(typescriptSnippets).toBeUndefined(); + + const rubySnippets = snippets?.ruby; + expect(rubySnippets).toBeDefined(); + expect(rubySnippets).toHaveLength(1); + + expect(example?.snippets).toMatchSnapshot(); + }); }); diff --git a/packages/fdr-sdk/src/api-definition/snippets/backfill.ts b/packages/fdr-sdk/src/api-definition/snippets/backfill.ts index 7b248c6d10..cc83271ca3 100644 --- a/packages/fdr-sdk/src/api-definition/snippets/backfill.ts +++ b/packages/fdr-sdk/src/api-definition/snippets/backfill.ts @@ -3,7 +3,7 @@ import { HTTPSnippet, type TargetId } from "httpsnippet-lite"; import { SnippetResolver } from "@fern-api/snippets"; -import type { DynamicIr } from "../../client/APIV1Write"; +import type { DynamicIr, EnvironmentId } from "../../client/APIV1Write"; import type { ApiDefinition, CodeSnippet, EndpointDefinition, ExampleEndpointCall } from "../latest"; import { toSnippetHttpRequest } from "./SnippetHttpRequest"; import { convertToCurl } from "./curl"; @@ -26,18 +26,33 @@ const CLIENTS: HTTPSnippetClient[] = [ { targetId: "swift", clientId: "nsurlsession" } ]; -export async function backfillSnippets( - apiDefinition: ApiDefinition, - dynamicIr: DynamicIRsByLanguage | undefined, - flags: { - isHttpSnippetsEnabled: boolean; - alwaysEnableJavaScriptFetch: boolean; - } -): Promise { +export async function backfillSnippets({ + apiDefinition, + dynamicIr, + httpSnippets, + alwaysEnableJavaScriptFetch +}: { + apiDefinition: ApiDefinition; + dynamicIr: DynamicIRsByLanguage | undefined; + httpSnippets: boolean | string[]; + alwaysEnableJavaScriptFetch: boolean; +}): Promise { + const snippetLanguages = createSnippetLanguages({ + isHttpSnippetsEnabled: typeof httpSnippets === "boolean" ? httpSnippets : httpSnippets?.length > 0, + snippetLanguages: Array.isArray(httpSnippets) ? httpSnippets : undefined + }); + return { ...apiDefinition, endpoints: await Promise.all( Object.entries(apiDefinition.endpoints).map(async ([id, endpoint]) => { + if (endpoint.environments?.length === 0) { + endpoint.environments?.push({ + id: "Default" as EnvironmentId, + baseUrl: "https://host.com" + }); + } + let dynamicGenerators: Record = {}; if (dynamicIr) { dynamicGenerators = createSnippetGenerators({ endpoint, dynamicIr }); @@ -49,7 +64,14 @@ export async function backfillSnippets( ...endpoint, examples: await Promise.all( endpoint.examples?.map((example) => - backfillSnippetsForExample(apiDefinition, dynamicGenerators, endpoint, example, flags) + backfillSnippetsForExample({ + apiDefinition, + dynamicGenerators, + endpoint, + example, + alwaysEnableJavaScriptFetch, + snippetLanguages + }) ) ?? [] ) } @@ -59,19 +81,21 @@ export async function backfillSnippets( }; } -async function backfillSnippetsForExample( - apiDefinition: ApiDefinition, - dynamicGenerators: Record, - endpoint: EndpointDefinition, - example: ExampleEndpointCall, - { - isHttpSnippetsEnabled, - alwaysEnableJavaScriptFetch - }: { - isHttpSnippetsEnabled: boolean; - alwaysEnableJavaScriptFetch: boolean; - } -): Promise { +async function backfillSnippetsForExample({ + apiDefinition, + dynamicGenerators, + endpoint, + example, + alwaysEnableJavaScriptFetch, + snippetLanguages +}: { + apiDefinition: ApiDefinition; + dynamicGenerators: Record; + endpoint: EndpointDefinition; + example: ExampleEndpointCall; + alwaysEnableJavaScriptFetch: boolean; + snippetLanguages: string[]; +}): Promise { const snippets = { ...example.snippets }; const pushSnippet = (snippet: CodeSnippet) => { @@ -79,7 +103,7 @@ async function backfillSnippetsForExample( }; // Check if curl snippet exists - if (!snippets.curl?.length) { + if (!snippets.curl?.length && snippetLanguages.includes("curl")) { const endpointAuth = endpoint.auth?.[0]; const curlCode = convertToCurl( toSnippetHttpRequest( @@ -98,9 +122,13 @@ async function backfillSnippetsForExample( }); } - if (isHttpSnippetsEnabled) { + if (snippetLanguages.length > 0) { const snippet = new HTTPSnippet(getHarRequest(endpoint, example, apiDefinition.auths, example.requestBody)); for (const { clientId, targetId } of CLIENTS) { + if (!snippetLanguages.includes(targetId)) { + continue; + } + /** * If the snippet already exists, skip it */ @@ -327,3 +355,23 @@ function createSnippetGenerators({ return generators; } + +// create the list of languages we should backfill snippets for +function createSnippetLanguages({ + isHttpSnippetsEnabled, + snippetLanguages +}: { + isHttpSnippetsEnabled: boolean; + snippetLanguages: string[] | undefined; +}): string[] { + // if client-defined snippet list, do not assume we should include curl + if (isHttpSnippetsEnabled && snippetLanguages) { + return snippetLanguages.map((lang) => (lang === "typescript" ? "javascript" : lang)); + } + + if (isHttpSnippetsEnabled) { + return [...CLIENTS.map((language) => language.targetId), "curl"]; + } + + return ["curl"]; +}