Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add header parsing for headers in individual routes #27

Merged
merged 4 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Added support for adding secruity param as a query param in `api.ts` ([#27](https://github.com/hasura/ndc-open-api-lambda/pull/27))
- Added support for `@save` annotation to preserve user's changes ([#24](https://github.com/hasura/ndc-open-api-lambda/pull/24))
- Bug fix: Replaced `*/` with `*''/` in API descriptions so that multi line comments do not end pre maturely which was resulting in a syntax error ([#21](https://github.com/hasura/ndc-open-api-lambda/pull/21))

Expand Down
11 changes: 7 additions & 4 deletions src/app/generator/api-ts-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import * as legacyApiTsGenerator from "../parser/open-api/api-generator";
import * as functionParamsParser from "../parser/open-api/types-parser";
import * as openApiParser from "../parser/open-api/open-api-parser";
import * as types from "./types";
import * as schemaTypes from "../parser/open-api/types";
import * as routeTypes from "../parser/open-api/route-types";

export async function generateApiTsCode(
openApiUri: string,
): Promise<types.GeneratedApiTsCode> {
const generatedSchemaComponents: swaggerTypescriptApi.SchemaComponent[] = [];
const generatedRoutes: swaggerTypescriptApi.ParsedRoute[] = [];
const generatedSchemaComponents: schemaTypes.Schema[] = [];
const generatedRoutes: routeTypes.ApiRoute[] = [];
const generatedTypeNames: types.GeneratedTypeName[] = [];

let openApiUrl: string = "";
Expand All @@ -36,12 +38,13 @@ export async function generateApiTsCode(
* Contains the full definition of the type, along with individual variables in objects
*/
onCreateComponent: (component) => {
generatedSchemaComponents.push(component);
const anyComponent: any = component;
generatedSchemaComponents.push(anyComponent as schemaTypes.Schema);
},

onCreateRoute: (routeData) => {
const route = processApiRoute(routeData, typedOpenApiComponents);
generatedRoutes.push(routeData);
generatedRoutes.push(routeData as routeTypes.ApiRoute);
return route;
},

Expand Down
6 changes: 4 additions & 2 deletions src/app/generator/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as swaggerTypescriptApi from "swagger-typescript-api";
import * as legacyApiTsGenerator from "../parser/open-api/api-generator";
import { ParsedSchemaStore } from "../parser/open-api/schema-parser";
import * as schemaTypes from "../parser/open-api/types";
import * as routeTypes from "../parser/open-api/route-types";

export type GeneratedApiTsCode = {
legacyTypedApiComponents: legacyApiTsGenerator.ApiComponents;
schemaComponents: swaggerTypescriptApi.SchemaComponent[];
routes: swaggerTypescriptApi.ParsedRoute[];
schemaComponents: schemaTypes.Schema[];
routes: routeTypes.ApiRoute[];
typeNames: GeneratedTypeName[];
files: swaggerTypescriptApi.GenerateApiOutput;
schemaStore?: ParsedSchemaStore;
Expand Down
85 changes: 85 additions & 0 deletions src/app/parser/open-api/header-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as context from "../../context";
import * as headerParser from "./header-parser";
import * as assert from "assert";
import path from "path";
import * as apiGenerator from "../../generator/api-ts-generator";
import * as routeTypes from "./route-types";
import * as fs from "fs";

context.getInstance().setLogLevel(context.LogLevel.ERROR);

type Test = {
name: string;
openApiFile: string;
goldenFile: string;
expected?: Map<string, string[] | undefined>;
routes?: routeTypes.ApiRoute[];
};

const tests: Test[] = [
{
name: "Petstore",
openApiFile: "petstore.yaml",
goldenFile: "petstore.json",
},
{
name: "aws-autoscaling",
openApiFile: "aws-autoscaling.json",
goldenFile: "aws-autoscaling.json",
},
];

function getRouteId(routeChars: routeTypes.BasicRouteCharacterstics) {
return `${routeChars.method}__${routeChars.route}`;
}

describe("header-parser", function () {
for (const testCase of tests) {
before(async () => {
testCase.openApiFile = path.resolve(
__dirname,
"../../../../tests/test-data/open-api-docs",
testCase.openApiFile,
);

const generatedCode = await apiGenerator.generateApiTsCode(
testCase.openApiFile,
);
testCase.routes = generatedCode.routes;

testCase.goldenFile = path.resolve(
__dirname,
"./test-data/golden-files/header-parser-tests",
testCase.goldenFile,
);

try {
testCase.expected = new Map(
Object.entries(
JSON.parse(fs.readFileSync(testCase.goldenFile).toString()),
),
);
} catch (e) {
testCase.expected = new Map<string, string[] | undefined>();
}
});

it(`header-parser::${testCase.name}`, function () {
const got: Map<string, string[] | undefined> = new Map<
string,
string[] | undefined
>();
for (const route of testCase.routes!) {
const headers = headerParser.parseRouteHeaders(route);
const routeId = getRouteId(routeTypes.getBasicCharacteristics(route));

got.set(routeId, headers ? Array.from(headers) : []);
}

assert.deepEqual(got, testCase.expected);

// uncomment to update golden file
// fs.writeFileSync(testCase.goldenFile, JSON.stringify(Object.fromEntries(got)));
});
}
});
26 changes: 26 additions & 0 deletions src/app/parser/open-api/header-parser.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import * as schemaTypes from "./types";
import * as routeTypes from "./route-types";
import * as logger from "../../../util/logger";

/**
*
* @param headersString The list of headers in the format: `key1=value1&key2=value2&key3=value3`
Expand All @@ -18,3 +22,25 @@ export function parseHeaders(
}
return headerMap;
}

export function parseRouteHeaders(
route: routeTypes.ApiRoute,
): routeTypes.ParsedHeaders | undefined {
if (!route.headersObjectSchema) {
return undefined;
}
if (!schemaTypes.schemaPropertyIsTypeObject(route.headersObjectSchema)) {
const basicChars = routeTypes.getBasicCharacteristics(route);
logger.error(
`Cannot parse headers for API Route: ${basicChars.method} ${basicChars.route}\nERROR: route.headersObjectSchema is not of type object`,
);
return undefined;
}
const headers: Set<string> = new Set<string>(
Object.keys(route.headersObjectSchema.properties),
);
if (headers.size === 0) {
return undefined;
}
m-Bilal marked this conversation as resolved.
Show resolved Hide resolved
return headers;
}
33 changes: 33 additions & 0 deletions src/app/parser/open-api/route-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as swaggerTypescriptApi from "swagger-typescript-api";
import * as schemaTypes from "./types";

export type ApiRoute = swaggerTypescriptApi.ParsedRoute & {
headersObjectSchema: Headers | undefined;
};

export type Headers = schemaTypes.SchemaProperty;

export type BasicRouteCharacterstics = {
route: string;
method: string;
usage: string;
};

export function getBasicCharacteristics(
route: ApiRoute,
): BasicRouteCharacterstics {
return {
route: route.raw.route,
method: route.raw.method,
usage: route.routeName.usage,
};
}

export type ParsedApiRoute = {
originalRoute: ApiRoute;

basicCharacteristics: BasicRouteCharacterstics;
headers: ParsedHeaders | undefined;
};

export type ParsedHeaders = Set<string>;
6 changes: 3 additions & 3 deletions src/app/parser/open-api/schema-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as logger from "../../../util/logger";

export function getParsedSchemaStore(
typeNames: generatorTypes.GeneratedTypeName[],
schemaComponents: swaggerTypescriptApi.SchemaComponent[],
schemaComponents: parserTypes.Schema[],
): ParsedSchemaStore {
const mappings: Mappings = {
rawTypeToTypeNameMap: new Map<string, string>(),
Expand All @@ -25,11 +25,11 @@ export function getParsedSchemaStore(
* compiler does not think that component can be correctly cast to parserTypes.Schema
*/
// @ts-ignore
if (!shouldParseScehma(component as parserTypes.Schema)) {
if (!shouldParseScehma(component)) {
continue;
}
// @ts-ignore
createSchemaMapping(mappings, component as parserTypes.Schema);
createSchemaMapping(mappings, component);
}

const schemaStore = new ParsedSchemaStore(mappings);
Expand Down
Loading