From 4520edf6734a96cb8a7f6bdbce5f8b83cf00f016 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Mon, 13 Jan 2025 21:53:44 -0500 Subject: [PATCH] feat: Global headers and better enum parsing in OpenApi Parser v2 (#1995) --- packages/parsers/package.json | 2 +- .../3.1/OpenApiDocumentConverter.node.ts | 12 +- .../XFernGlobalHeadersConverter.node.ts | 67 +++++++++++ .../XFernGlobalHeadersConverter.node.test.ts | 104 ++++++++++++++++++ .../3.1/extensions/fernExtension.consts.ts | 1 + .../schemas/primitives/EnumConverter.node.ts | 18 +++ .../__test__/EnumConverter.node.test.ts | 29 ++++- .../__test__/__snapshots__/uploadcare.json | 30 ++++- .../__test__/fixtures/uploadcare/openapi.yml | 30 ++--- 9 files changed, 273 insertions(+), 20 deletions(-) create mode 100644 packages/parsers/src/openapi/3.1/extensions/XFernGlobalHeadersConverter.node.ts create mode 100644 packages/parsers/src/openapi/3.1/extensions/__test__/XFernGlobalHeadersConverter.node.test.ts diff --git a/packages/parsers/package.json b/packages/parsers/package.json index 7da1130aca..6ce335de25 100644 --- a/packages/parsers/package.json +++ b/packages/parsers/package.json @@ -1,6 +1,6 @@ { "name": "@fern-api/docs-parsers", - "version": "0.0.25", + "version": "0.0.29", "repository": { "type": "git", "url": "https://github.com/fern-api/fern-platform.git", diff --git a/packages/parsers/src/openapi/3.1/OpenApiDocumentConverter.node.ts b/packages/parsers/src/openapi/3.1/OpenApiDocumentConverter.node.ts index 1d42c7717b..b84547a5f4 100644 --- a/packages/parsers/src/openapi/3.1/OpenApiDocumentConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/OpenApiDocumentConverter.node.ts @@ -10,6 +10,7 @@ import { import { coalesceServers } from "../utils/3.1/coalesceServers"; import { SecurityRequirementObjectConverterNode } from "./auth/SecurityRequirementObjectConverter.node"; import { XFernBasePathConverterNode } from "./extensions/XFernBasePathConverter.node"; +import { XFernGlobalHeadersConverterNode } from "./extensions/XFernGlobalHeadersConverter.node"; import { XFernGroupsConverterNode } from "./extensions/XFernGroupsConverter.node"; import { PathsObjectConverterNode } from "./paths/PathsObjectConverter.node"; import { ServerObjectConverterNode } from "./paths/ServerObjectConverter.node"; @@ -29,6 +30,7 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode< basePath: XFernBasePathConverterNode | undefined; fernGroups: XFernGroupsConverterNode | undefined; tags: TagObjectConverterNode[] | undefined; + globalHeaders: XFernGlobalHeadersConverterNode | undefined; constructor( args: BaseOpenApiV3_1ConverterNodeConstructorArgs @@ -128,6 +130,13 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode< pathId: "components", }); } + + this.globalHeaders = new XFernGlobalHeadersConverterNode({ + input: this.input, + context: this.context, + accessPath: this.accessPath, + pathId: "x-fern-global-headers", + }); } convert(): FernRegistry.api.latest.ApiDefinition | undefined { @@ -188,8 +197,7 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode< // This is not necessary and will be removed subpackages, auths: this.auth?.convert() ?? {}, - // TODO: Implement globalHeaders - globalHeaders: undefined, + globalHeaders: this.globalHeaders?.convert(), }; } } diff --git a/packages/parsers/src/openapi/3.1/extensions/XFernGlobalHeadersConverter.node.ts b/packages/parsers/src/openapi/3.1/extensions/XFernGlobalHeadersConverter.node.ts new file mode 100644 index 0000000000..2af6481719 --- /dev/null +++ b/packages/parsers/src/openapi/3.1/extensions/XFernGlobalHeadersConverter.node.ts @@ -0,0 +1,67 @@ +import { isNonNullish } from "@fern-api/ui-core-utils"; +import { OpenAPIV3_1 } from "openapi-types"; +import { FernRegistry } from "../../../client/generated"; +import { + BaseOpenApiV3_1ConverterNode, + BaseOpenApiV3_1ConverterNodeConstructorArgs, +} from "../../BaseOpenApiV3_1Converter.node"; +import { extendType } from "../../utils/extendType"; +import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray"; +import { SchemaConverterNode } from "../schemas"; +import { X_FERN_GLOBAL_HEADERS } from "./fernExtension.consts"; + +export declare namespace XFernGlobalHeadersConverterNode { + export interface Input { + [X_FERN_GLOBAL_HEADERS]?: (( + | OpenAPIV3_1.SchemaObject + | OpenAPIV3_1.ReferenceObject + ) & { + header: string; + })[]; + } +} + +export class XFernGlobalHeadersConverterNode extends BaseOpenApiV3_1ConverterNode< + unknown, + FernRegistry.api.latest.ObjectProperty[] +> { + globalHeaders?: [string, SchemaConverterNode][] | undefined; + + constructor(args: BaseOpenApiV3_1ConverterNodeConstructorArgs) { + super(args); + this.safeParse(); + } + + // This would be used to set a member on the node + parse(): void { + this.globalHeaders = extendType( + this.input + )[X_FERN_GLOBAL_HEADERS]?.map((header) => { + const { header: headerName, ...schema } = header; + return [ + headerName, + new SchemaConverterNode({ + input: schema, + context: this.context, + accessPath: this.accessPath, + pathId: this.pathId, + }), + ]; + }); + } + + convert(): FernRegistry.api.latest.ObjectProperty[] | undefined { + return this.globalHeaders + ?.flatMap(([headerName, headerSchema]) => { + const convertedSchema = maybeSingleValueToArray(headerSchema.convert()); + + return convertedSchema?.map((schema) => ({ + key: FernRegistry.PropertyKey(headerName), + valueShape: schema, + description: headerSchema.description, + availability: headerSchema.availability?.convert(), + })); + }) + .filter(isNonNullish); + } +} diff --git a/packages/parsers/src/openapi/3.1/extensions/__test__/XFernGlobalHeadersConverter.node.test.ts b/packages/parsers/src/openapi/3.1/extensions/__test__/XFernGlobalHeadersConverter.node.test.ts new file mode 100644 index 0000000000..e221faaae2 --- /dev/null +++ b/packages/parsers/src/openapi/3.1/extensions/__test__/XFernGlobalHeadersConverter.node.test.ts @@ -0,0 +1,104 @@ +import { createMockContext } from "../../../__test__/createMockContext.util"; +import { X_FERN_GLOBAL_HEADERS } from "../fernExtension.consts"; +import { XFernGlobalHeadersConverterNode } from "../XFernGlobalHeadersConverter.node"; + +describe("XFernGlobalHeadersConverterNode", () => { + const mockContext = createMockContext(); + const mockAccessPath = ["test", "path"]; + const mockPathId = "test-path"; + + beforeEach(() => { + mockContext.errors.warning.mockClear(); + }); + + it("should parse global headers correctly", () => { + const input = { + [X_FERN_GLOBAL_HEADERS]: [ + { + header: "X-API-Key", + type: "string", + description: "API Key for authentication", + }, + ], + }; + + const converter = new XFernGlobalHeadersConverterNode({ + input, + context: mockContext, + accessPath: mockAccessPath, + pathId: mockPathId, + }); + + expect(converter.globalHeaders).toBeDefined(); + expect(converter.globalHeaders?.length).toBe(1); + expect(converter.globalHeaders?.[0][0]).toBe("X-API-Key"); + }); + + it("should handle empty input", () => { + const input = {}; + + const converter = new XFernGlobalHeadersConverterNode({ + input, + context: mockContext, + accessPath: mockAccessPath, + pathId: mockPathId, + }); + + expect(converter.globalHeaders).toBeUndefined(); + }); + + it("should convert global headers to ObjectProperties", () => { + const input = { + [X_FERN_GLOBAL_HEADERS]: [ + { + header: "X-API-Key", + type: "string", + description: "API Key for authentication", + }, + ], + }; + + const converter = new XFernGlobalHeadersConverterNode({ + input, + context: mockContext, + accessPath: mockAccessPath, + pathId: mockPathId, + }); + + const result = converter.convert(); + + expect(result).toBeDefined(); + expect(result?.length).toBe(1); + expect(result?.[0].key).toBe("X-API-Key"); + expect(result?.[0].description).toBe("API Key for authentication"); + }); + + it("should handle multiple global headers", () => { + const input = { + [X_FERN_GLOBAL_HEADERS]: [ + { + header: "X-API-Key", + type: "string", + }, + { + header: "X-Client-ID", + type: "string", + }, + ], + }; + + const converter = new XFernGlobalHeadersConverterNode({ + input, + context: mockContext, + accessPath: mockAccessPath, + pathId: mockPathId, + }); + + const result = converter.convert(); + + expect(result).toBeDefined(); + expect(result?.length).toBe(2); + expect(result?.[0].key).toBe("X-API-Key"); + expect(result?.[1].key).toBe("X-Client-ID"); + }); +}); diff --git a/packages/parsers/src/openapi/3.1/extensions/fernExtension.consts.ts b/packages/parsers/src/openapi/3.1/extensions/fernExtension.consts.ts index ec8d1c6c93..0338a16332 100644 --- a/packages/parsers/src/openapi/3.1/extensions/fernExtension.consts.ts +++ b/packages/parsers/src/openapi/3.1/extensions/fernExtension.consts.ts @@ -1,6 +1,7 @@ export const X_FERN_BASE_PATH = "x-fern-base-path"; export const X_FERN_AVAILABILITY = "x-fern-availability"; export const X_FERN_GROUP_NAME = "x-fern-sdk-group-name"; +export const X_FERN_GLOBAL_HEADERS = "x-fern-global-headers"; export const X_FERN_SDK_METHOD_NAME = "x-fern-sdk-method-name"; export const X_FERN_GROUPS = "x-fern-groups"; export const X_FERN_WEBHOOK = "x-fern-webhook"; diff --git a/packages/parsers/src/openapi/3.1/schemas/primitives/EnumConverter.node.ts b/packages/parsers/src/openapi/3.1/schemas/primitives/EnumConverter.node.ts index 15493cfaa5..48016227e1 100644 --- a/packages/parsers/src/openapi/3.1/schemas/primitives/EnumConverter.node.ts +++ b/packages/parsers/src/openapi/3.1/schemas/primitives/EnumConverter.node.ts @@ -5,6 +5,8 @@ import { BaseOpenApiV3_1ConverterNodeConstructorArgs, BaseOpenApiV3_1ConverterNodeWithExample, } from "../../../BaseOpenApiV3_1Converter.node"; +import { resolveSchemaReference } from "../../../utils/3.1/resolveSchemaReference"; +import { isReferenceObject } from "../../guards/isReferenceObject"; export class EnumConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample< OpenAPIV3_1.NonArraySchemaObject, @@ -29,6 +31,22 @@ export class EnumConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample< return undefined; } + if (isReferenceObject(value)) { + const resolvedReference = resolveSchemaReference( + value, + this.context.document + ); + + value = + typeof resolvedReference === "string" + ? resolvedReference + : typeof resolvedReference?.default === "string" + ? resolvedReference?.default + : typeof resolvedReference?.example === "string" + ? resolvedReference.example + : undefined; + } + // TODO: Support { name?: .., description?: .., casing?: .. } here as well if (typeof value !== "string") { this.context.errors.error({ diff --git a/packages/parsers/src/openapi/3.1/schemas/primitives/__test__/EnumConverter.node.test.ts b/packages/parsers/src/openapi/3.1/schemas/primitives/__test__/EnumConverter.node.test.ts index 81b2b5c228..253100bd6e 100644 --- a/packages/parsers/src/openapi/3.1/schemas/primitives/__test__/EnumConverter.node.test.ts +++ b/packages/parsers/src/openapi/3.1/schemas/primitives/__test__/EnumConverter.node.test.ts @@ -1,9 +1,16 @@ import { OpenAPIV3_1 } from "openapi-types"; +import { expect } from "vitest"; import { createMockContext } from "../../../../__test__/createMockContext.util"; import { EnumConverterNode } from "../EnumConverter.node"; describe("EnumConverterNode", () => { - const mockContext = createMockContext(); + const mockContext = createMockContext({ + components: { + schemas: { + Status: { type: "string", default: "ACTIVE" }, + }, + }, + } as unknown as OpenAPIV3_1.Document); beforeEach(() => { vi.clearAllMocks(); @@ -70,6 +77,26 @@ describe("EnumConverterNode", () => { path: ["test", "enum[1]"], }); }); + + it("should handle enum schema with $ref", () => { + const input: OpenAPIV3_1.SchemaObject = { + enum: [{ $ref: "#/components/schemas/Status" }], + }; + const node = new EnumConverterNode({ + input, + context: mockContext, + accessPath: [], + pathId: "test", + }); + expect(node.convert()).toEqual({ + type: "enum", + values: [ + { + value: "ACTIVE", + }, + ], + }); + }); }); describe("convert", () => { diff --git a/packages/parsers/src/openapi/__test__/__snapshots__/uploadcare.json b/packages/parsers/src/openapi/__test__/__snapshots__/uploadcare.json index 6a0c1375d0..c12b9e0e3d 100644 --- a/packages/parsers/src/openapi/__test__/__snapshots__/uploadcare.json +++ b/packages/parsers/src/openapi/__test__/__snapshots__/uploadcare.json @@ -15496,5 +15496,33 @@ "headerWireValue": "Authorization", "prefix": "Uploadcare" } - } + }, + "globalHeaders": [ + { + "key": "Authorization", + "valueShape": { + "type": "alias", + "value": { + "type": "primitive", + "value": { + "type": "string" + } + } + }, + "description": "With the `Uploadcare` authentication method:\n* `auth-param` is a `public_key:signature` pair, where your `secret_key` is used to derive `signature` but is _not included in every request_ itself.\n* You MUST also provide the `Date` header in [RFC2822](https://datatracker.ietf.org/doc/html/rfc2822#section-3.3) format with the time zone set to `GMT` (see the example below).\n* The date you provide MUST NOT exceed the 15-minute offset from the server time of the API endpoint.\n\n```http\nAccept: application/vnd.uploadcare-v0.7+json\nDate: Fri, 30 Sep 2016 11:10:54 GMT\nAuthorization: Uploadcare public_key:6ff75027649aadd4dc98c1f784444445d1e6ed82\n```\n\nThe `signature` part of the `Uploadcare` authentication method `auth-param` MUST be constructed from the following components:\n* Request type (`POST`, `GET`, `HEAD`, `OPTIONS`)\n* Hex md5 hash of the request body\n* `Content-Type` header value\n* `Date` header value\n* URI including path and parameters\n\nThe parameters are then concatenated in textual order using LF: every value sits in a separate line. The result is then signed with [HMAC/SHA1](https://en.wikipedia.org/wiki/HMAC) using your project's `secret_key`.\n\nTake a look at the Python example of deriving `signature`; the example request is made to get a list of files:\n\n```py\nimport time\nimport hashlib\nimport hmac\nfrom email import utils\n\n# Specifying the project’s key\nSECRET_KEY = 'YOUR_SECRET_KEY'\n\n# Specifying request type\nverb = 'GET'\n\n# Calculate [md5](https://en.wikipedia.org/wiki/MD5) checksum for the request's HTTP body.\n# Note: Taking into account that in our example, we are sending an HTTP GET request,\n# and the request does not have anything in its HTTP body, we use an empty string as an input to the md5 hash function.\n# If we were to send an HTTP POST request with, for example, JSON in the request's body,\n# we would have to pass the JSON as the input to the md5 hash function.\ncontent_md5 = hashlib.md5(b'').hexdigest()\n\n# Content-Type header\ncontent_type = 'application/json'\n\n# Current time, e.g. 1541423681\ntimestamp = int(time.time())\n# Date header ('Mon, 05 Nov 2018 13:14:41 GMT')\ndate_header = utils.formatdate(timestamp, usegmt=True)\n\n# The request URI\nuri = '/files/?limit=1&stored=true'\n\n# Forming the final string: concatenating\nsign_string = '\\n'.join([verb, content_md5, content_type, date_header, uri])\n\n# Calculating the signature,\n# the result may look like this: \"3cbc4d2cf91f80c1ba162b926f8a975e8bec7995\"\nsignature = hmac.new(SECRET_KEY.encode(), sign_string.encode(), hashlib.sha1).hexdigest()\n```\n\nOnce `signature` is derived, it SHOULD be implemented into the request body:\n\n```bash\ncurl \\\n -H 'Content-Type: application/json' \\\n -H 'Accept: application/vnd.uploadcare-v0.7+json' \\\n -H 'Date: Mon, 05 Nov 2018 13:14:41 GMT' \\\n -H 'Authorization: Uploadcare YOUR_PUBLIC_KEY:SIGNATURE' \\\n 'https://api.uploadcare.com/files/?limit=1&stored=true'\n```\n" + }, + { + "key": "Authorization", + "valueShape": { + "type": "alias", + "value": { + "type": "primitive", + "value": { + "type": "string" + } + } + }, + "description": "Note: We DO NOT recommend using this authentication method in production.\n\nWith the `Uploadcare.Simple` authentication method, `auth-param` is your `public_key:secret_key` pair. Note that in this scheme, your Uploadcare project `secret_key` is _included in every request as plain text_.\n\n```http\nAccept: application/vnd.uploadcare-v0.7+json\nAuthorization: Uploadcare.Simple public_key:secret_key\n```\n" + } + ] } \ No newline at end of file diff --git a/packages/parsers/src/openapi/__test__/fixtures/uploadcare/openapi.yml b/packages/parsers/src/openapi/__test__/fixtures/uploadcare/openapi.yml index 5e2d74623a..5248c39f91 100644 --- a/packages/parsers/src/openapi/__test__/fixtures/uploadcare/openapi.yml +++ b/packages/parsers/src/openapi/__test__/fixtures/uploadcare/openapi.yml @@ -47,6 +47,20 @@ }, ], "security": [{ "apiKeyAuth": [] }], + "x-fern-global-headers": [ + { + "type": "string", + "header": "Authorization", + "name": "Uploadcare", + "description": "With the `Uploadcare` authentication method:\n* `auth-param` is a `public_key:signature` pair, where your `secret_key` is used to derive `signature` but is _not included in every request_ itself.\n* You MUST also provide the `Date` header in [RFC2822](https://datatracker.ietf.org/doc/html/rfc2822#section-3.3) format with the time zone set to `GMT` (see the example below).\n* The date you provide MUST NOT exceed the 15-minute offset from the server time of the API endpoint.\n\n```http\nAccept: application/vnd.uploadcare-v0.7+json\nDate: Fri, 30 Sep 2016 11:10:54 GMT\nAuthorization: Uploadcare public_key:6ff75027649aadd4dc98c1f784444445d1e6ed82\n```\n\nThe `signature` part of the `Uploadcare` authentication method `auth-param` MUST be constructed from the following components:\n* Request type (`POST`, `GET`, `HEAD`, `OPTIONS`)\n* Hex md5 hash of the request body\n* `Content-Type` header value\n* `Date` header value\n* URI including path and parameters\n\nThe parameters are then concatenated in textual order using LF: every value sits in a separate line. The result is then signed with [HMAC/SHA1](https://en.wikipedia.org/wiki/HMAC) using your project's `secret_key`.\n\nTake a look at the Python example of deriving `signature`; the example request is made to get a list of files:\n\n```py\nimport time\nimport hashlib\nimport hmac\nfrom email import utils\n\n# Specifying the project’s key\nSECRET_KEY = 'YOUR_SECRET_KEY'\n\n# Specifying request type\nverb = 'GET'\n\n# Calculate [md5](https://en.wikipedia.org/wiki/MD5) checksum for the request's HTTP body.\n# Note: Taking into account that in our example, we are sending an HTTP GET request,\n# and the request does not have anything in its HTTP body, we use an empty string as an input to the md5 hash function.\n# If we were to send an HTTP POST request with, for example, JSON in the request's body,\n# we would have to pass the JSON as the input to the md5 hash function.\ncontent_md5 = hashlib.md5(b'').hexdigest()\n\n# Content-Type header\ncontent_type = 'application/json'\n\n# Current time, e.g. 1541423681\ntimestamp = int(time.time())\n# Date header ('Mon, 05 Nov 2018 13:14:41 GMT')\ndate_header = utils.formatdate(timestamp, usegmt=True)\n\n# The request URI\nuri = '/files/?limit=1&stored=true'\n\n# Forming the final string: concatenating\nsign_string = '\\n'.join([verb, content_md5, content_type, date_header, uri])\n\n# Calculating the signature,\n# the result may look like this: \"3cbc4d2cf91f80c1ba162b926f8a975e8bec7995\"\nsignature = hmac.new(SECRET_KEY.encode(), sign_string.encode(), hashlib.sha1).hexdigest()\n```\n\nOnce `signature` is derived, it SHOULD be implemented into the request body:\n\n```bash\ncurl \\\n -H 'Content-Type: application/json' \\\n -H 'Accept: application/vnd.uploadcare-v0.7+json' \\\n -H 'Date: Mon, 05 Nov 2018 13:14:41 GMT' \\\n -H 'Authorization: Uploadcare YOUR_PUBLIC_KEY:SIGNATURE' \\\n 'https://api.uploadcare.com/files/?limit=1&stored=true'\n```\n", + }, + { + "type": "string", + "header": "Authorization", + "name": "Uploadcare.Simple", + "description": "Note: We DO NOT recommend using this authentication method in production.\n\nWith the `Uploadcare.Simple` authentication method, `auth-param` is your `public_key:secret_key` pair. Note that in this scheme, your Uploadcare project `secret_key` is _included in every request as plain text_.\n\n```http\nAccept: application/vnd.uploadcare-v0.7+json\nAuthorization: Uploadcare.Simple public_key:secret_key\n```\n", + }, + ], "paths": { "/files/": @@ -2908,21 +2922,7 @@ "name": "Authorization", "x-fern-header": { "prefix": "Uploadcare" }, "description": "Every request made to `https://api.uploadcare.com/` MUST be signed. HTTPS SHOULD be used with any authorization scheme.\n\nRequests MUST contain the `Authorization` header defining `auth-scheme` and `auth-param`: `Authorization: auth-scheme auth-param`.\n\nEvery request MUST contain the `Accept` header identifying the REST API version: `Accept: application/vnd.uploadcare-v0.7+json`.\n\nThere are two available authorization schemes:\n* For production: `Uploadcare`, a scheme where a `signature`, not your Secret API Key MUST be specified. Signatures SHOULD be generated on backend.\n* For quick tests: `Uploadcare.Simple`, a simple scheme where your [Secret API Key](https://app.uploadcare.com/projects/-/api-keys/) MUST be specified in every request's `auth-param`.\n", - }, - "Uploadcare": - { - "type": "apiKey", - "in": "header", - "name": "Uploadcare", - "description": "With the `Uploadcare` authentication method:\n* `auth-param` is a `public_key:signature` pair, where your `secret_key` is used to derive `signature` but is _not included in every request_ itself.\n* You MUST also provide the `Date` header in [RFC2822](https://datatracker.ietf.org/doc/html/rfc2822#section-3.3) format with the time zone set to `GMT` (see the example below).\n* The date you provide MUST NOT exceed the 15-minute offset from the server time of the API endpoint.\n\n```http\nAccept: application/vnd.uploadcare-v0.7+json\nDate: Fri, 30 Sep 2016 11:10:54 GMT\nAuthorization: Uploadcare public_key:6ff75027649aadd4dc98c1f784444445d1e6ed82\n```\n\nThe `signature` part of the `Uploadcare` authentication method `auth-param` MUST be constructed from the following components:\n* Request type (`POST`, `GET`, `HEAD`, `OPTIONS`)\n* Hex md5 hash of the request body\n* `Content-Type` header value\n* `Date` header value\n* URI including path and parameters\n\nThe parameters are then concatenated in textual order using LF: every value sits in a separate line. The result is then signed with [HMAC/SHA1](https://en.wikipedia.org/wiki/HMAC) using your project's `secret_key`.\n\nTake a look at the Python example of deriving `signature`; the example request is made to get a list of files:\n\n```py\nimport time\nimport hashlib\nimport hmac\nfrom email import utils\n\n# Specifying the project’s key\nSECRET_KEY = 'YOUR_SECRET_KEY'\n\n# Specifying request type\nverb = 'GET'\n\n# Calculate [md5](https://en.wikipedia.org/wiki/MD5) checksum for the request's HTTP body.\n# Note: Taking into account that in our example, we are sending an HTTP GET request,\n# and the request does not have anything in its HTTP body, we use an empty string as an input to the md5 hash function.\n# If we were to send an HTTP POST request with, for example, JSON in the request's body,\n# we would have to pass the JSON as the input to the md5 hash function.\ncontent_md5 = hashlib.md5(b'').hexdigest()\n\n# Content-Type header\ncontent_type = 'application/json'\n\n# Current time, e.g. 1541423681\ntimestamp = int(time.time())\n# Date header ('Mon, 05 Nov 2018 13:14:41 GMT')\ndate_header = utils.formatdate(timestamp, usegmt=True)\n\n# The request URI\nuri = '/files/?limit=1&stored=true'\n\n# Forming the final string: concatenating\nsign_string = '\\n'.join([verb, content_md5, content_type, date_header, uri])\n\n# Calculating the signature,\n# the result may look like this: \"3cbc4d2cf91f80c1ba162b926f8a975e8bec7995\"\nsignature = hmac.new(SECRET_KEY.encode(), sign_string.encode(), hashlib.sha1).hexdigest()\n```\n\nOnce `signature` is derived, it SHOULD be implemented into the request body:\n\n```bash\ncurl \\\n -H 'Content-Type: application/json' \\\n -H 'Accept: application/vnd.uploadcare-v0.7+json' \\\n -H 'Date: Mon, 05 Nov 2018 13:14:41 GMT' \\\n -H 'Authorization: Uploadcare YOUR_PUBLIC_KEY:SIGNATURE' \\\n 'https://api.uploadcare.com/files/?limit=1&stored=true'\n```\n", - }, - "Uploadcare.Simple": - { - "type": "apiKey", - "in": "header", - "name": "Uploadcare.Simple", - "description": "Note: We DO NOT recommend using this authentication method in production.\n\nWith the `Uploadcare.Simple` authentication method, `auth-param` is your `public_key:secret_key` pair. Note that in this scheme, your Uploadcare project `secret_key` is _included in every request as plain text_.\n\n```http\nAccept: application/vnd.uploadcare-v0.7+json\nAuthorization: Uploadcare.Simple public_key:secret_key\n```\n", - }, + } }, "responses": {