Skip to content

Commit

Permalink
Update router.ts - adding operationId (#1349)
Browse files Browse the repository at this point in the history
* Update router.ts -  adding operationId

https://swagger.io/specification/#fixed-fields-8

operationId

Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.

* Use operationName

---------

Co-authored-by: Arda TANRIKULU <[email protected]>
  • Loading branch information
justinlevi and ardatan authored Nov 23, 2023
1 parent d4f9437 commit f21c5ad
Show file tree
Hide file tree
Showing 53 changed files with 2,542 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .bob/cjs/src/ast.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type DocumentNode, type OperationDefinitionNode, type VariableDefinitionNode } from 'graphql';
export type OperationInfo = {
operation: OperationDefinitionNode;
variables: ReadonlyArray<VariableDefinitionNode>;
name: string;
} | undefined;
export declare function getOperationInfo(doc: DocumentNode): OperationInfo;
16 changes: 16 additions & 0 deletions .bob/cjs/src/ast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOperationInfo = void 0;
const graphql_1 = require("graphql");
function getOperationInfo(doc) {
const op = (0, graphql_1.getOperationAST)(doc, null);
if (!op) {
return;
}
return {
operation: op,
name: op.name.value,
variables: op.variableDefinitions || [],
};
}
exports.getOperationInfo = getOperationInfo;
2 changes: 2 additions & 0 deletions .bob/cjs/src/common.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export declare function convertName(name: string): string;
export declare function isNil<T>(val: T): boolean;
12 changes: 12 additions & 0 deletions .bob/cjs/src/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isNil = exports.convertName = void 0;
const param_case_1 = require("param-case");
function convertName(name) {
return (0, param_case_1.paramCase)(name);
}
exports.convertName = convertName;
function isNil(val) {
return val == null;
}
exports.isNil = isNil;
5 changes: 5 additions & 0 deletions .bob/cjs/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { SofaConfig } from './sofa';
export { OpenAPI } from './open-api';
export declare function useSofa(config: SofaConfig): import("fets").Router<any, {}, {
[TKey: string]: never;
}>;
11 changes: 11 additions & 0 deletions .bob/cjs/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useSofa = exports.OpenAPI = void 0;
const router_1 = require("./router");
const sofa_1 = require("./sofa");
var open_api_1 = require("./open-api");
Object.defineProperty(exports, "OpenAPI", { enumerable: true, get: function () { return open_api_1.OpenAPI; } });
function useSofa(config) {
return (0, router_1.createRouter)((0, sofa_1.createSofa)(config));
}
exports.useSofa = useSofa;
6 changes: 6 additions & 0 deletions .bob/cjs/src/logger.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export declare const logger: {
error: (...args: any[]) => void;
warn: (...args: any[]) => void;
info: (...args: any[]) => void;
debug: (...args: any[]) => void;
};
29 changes: 29 additions & 0 deletions .bob/cjs/src/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = void 0;
const tslib_1 = require("tslib");
const ansi_colors_1 = tslib_1.__importDefault(require("ansi-colors"));
const levels = ['error', 'warn', 'info', 'debug'];
const toLevel = (string) => levels.includes(string) ? string : null;
const currentLevel = globalThis.process?.env?.SOFA_DEBUG
? 'debug'
: toLevel(globalThis.process?.env?.SOFA_LOGGER_LEVEL) ?? 'info';
const log = (level, color, args) => {
if (levels.indexOf(level) <= levels.indexOf(currentLevel)) {
console.log(`${color(level)}:`, ...args);
}
};
exports.logger = {
error: (...args) => {
log('error', ansi_colors_1.default.red, args);
},
warn: (...args) => {
log('warn', ansi_colors_1.default.yellow, args);
},
info: (...args) => {
log('info', ansi_colors_1.default.green, args);
},
debug: (...args) => {
log('debug', ansi_colors_1.default.blue, args);
},
};
26 changes: 26 additions & 0 deletions .bob/cjs/src/open-api/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { GraphQLSchema } from 'graphql';
import { RouteInfo } from '../types';
import { OpenAPIV3 } from 'openapi-types';
export declare function OpenAPI({ schema, info, servers, components, security, tags, customScalars, }: {
schema: GraphQLSchema;
info: OpenAPIV3.InfoObject;
servers?: OpenAPIV3.ServerObject[];
components?: Record<string, any>;
security?: OpenAPIV3.SecurityRequirementObject[];
tags?: OpenAPIV3.TagObject[];
/**
* Override mapping of custom scalars to OpenAPI
* @example
* ```js
* {
* Date: { type: "string", format: "date" }
* }
* ```
*/
customScalars?: Record<string, any>;
}): {
addRoute(info: RouteInfo, config?: {
basePath?: string;
}): void;
get(): OpenAPIV3.Document<{}>;
};
62 changes: 62 additions & 0 deletions .bob/cjs/src/open-api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAPI = void 0;
const graphql_1 = require("graphql");
const types_1 = require("./types");
const operations_1 = require("./operations");
const utils_1 = require("./utils");
function OpenAPI({ schema, info, servers, components, security, tags, customScalars = {}, }) {
const types = schema.getTypeMap();
const swagger = {
openapi: '3.0.0',
info,
servers,
tags: [],
paths: {},
components: {
schemas: {},
},
};
for (const typeName in types) {
const type = types[typeName];
if (((0, graphql_1.isObjectType)(type) || (0, graphql_1.isInputObjectType)(type)) &&
!(0, graphql_1.isIntrospectionType)(type)) {
swagger.components.schemas[typeName] = (0, types_1.buildSchemaObjectFromType)(type, {
customScalars,
});
}
}
if (components) {
swagger.components = { ...components, ...swagger.components };
}
if (security) {
swagger.security = security;
}
if (tags) {
swagger.tags = tags;
}
return {
addRoute(info, config) {
const basePath = config?.basePath || '';
const path = basePath +
(0, utils_1.normalizePathParamForOpenAPI)(info.path);
if (!swagger.paths[path]) {
swagger.paths[path] = {};
}
const pathsObj = swagger.paths[path];
pathsObj[info.method.toLowerCase()] = (0, operations_1.buildPathFromOperation)({
url: path,
operation: info.document,
schema,
useRequestBody: ['POST', 'PUT', 'PATCH'].includes(info.method),
tags: info.tags || [],
description: info.description || '',
customScalars,
});
},
get() {
return swagger;
},
};
}
exports.OpenAPI = OpenAPI;
29 changes: 29 additions & 0 deletions .bob/cjs/src/open-api/operations.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { DocumentNode, GraphQLSchema, OperationDefinitionNode, TypeNode, VariableDefinitionNode } from 'graphql';
import { OpenAPIV3 } from 'openapi-types';
export declare function buildPathFromOperation({ url, schema, operation, useRequestBody, tags, description, customScalars, }: {
url: string;
schema: GraphQLSchema;
operation: DocumentNode;
useRequestBody: boolean;
tags?: string[];
description?: string;
customScalars: Record<string, any>;
}): OpenAPIV3.OperationObject;
export declare function resolveRequestBody(variables: ReadonlyArray<VariableDefinitionNode> | undefined, schema: GraphQLSchema, operation: OperationDefinitionNode, opts: {
customScalars: Record<string, any>;
enumTypes: Record<string, any>;
}): {};
export declare function resolveParamSchema(type: TypeNode, opts: {
customScalars: Record<string, any>;
enumTypes: Record<string, any>;
}): any;
export declare function resolveResponse({ schema, operation, opts, }: {
schema: GraphQLSchema;
operation: OperationDefinitionNode;
opts: {
customScalars: Record<string, any>;
enumTypes: Record<string, any>;
};
}): any;
export declare function isInPath(url: string, param: string): boolean;
export declare function resolveVariableDescription(schema: GraphQLSchema, operation: OperationDefinitionNode, variable: VariableDefinitionNode): string | undefined;
164 changes: 164 additions & 0 deletions .bob/cjs/src/open-api/operations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveVariableDescription = exports.isInPath = exports.resolveResponse = exports.resolveParamSchema = exports.resolveRequestBody = exports.buildPathFromOperation = void 0;
const graphql_1 = require("graphql");
const ast_1 = require("../ast");
const utils_1 = require("./utils");
const types_1 = require("./types");
const title_case_1 = require("title-case");
function buildPathFromOperation({ url, schema, operation, useRequestBody, tags, description, customScalars, }) {
const info = (0, ast_1.getOperationInfo)(operation);
const enumTypes = resolveEnumTypes(schema);
const summary = resolveDescription(schema, info.operation);
const variables = info.operation.variableDefinitions;
const pathParams = variables?.filter((variable) => isInPath(url, variable.variable.name.value));
const bodyParams = variables?.filter((variable) => !isInPath(url, variable.variable.name.value));
return {
tags,
description,
summary,
operationId: info.name,
...(useRequestBody
? {
parameters: resolveParameters(url, pathParams, schema, info.operation, { customScalars, enumTypes }),
requestBody: {
content: {
'application/json': {
schema: resolveRequestBody(bodyParams, schema, info.operation, { customScalars, enumTypes }),
},
},
},
}
: {
parameters: resolveParameters(url, variables, schema, info.operation, { customScalars, enumTypes }),
}),
responses: {
200: {
description: summary,
content: {
'application/json': {
schema: resolveResponse({
schema,
operation: info.operation,
opts: { customScalars, enumTypes },
}),
},
},
},
},
};
}
exports.buildPathFromOperation = buildPathFromOperation;
function resolveEnumTypes(schema) {
const enumTypes = Object.values(schema.getTypeMap())
.filter(graphql_1.isEnumType);
return Object.fromEntries(enumTypes.map((type) => [
type.name,
{
type: 'string',
enum: type.getValues().map((value) => value.name),
},
]));
}
function resolveParameters(url, variables, schema, operation, opts) {
if (!variables) {
return [];
}
return variables.map((variable) => {
return {
in: isInPath(url, variable.variable.name.value) ? 'path' : 'query',
name: variable.variable.name.value,
required: variable.type.kind === graphql_1.Kind.NON_NULL_TYPE,
schema: resolveParamSchema(variable.type, opts),
description: resolveVariableDescription(schema, operation, variable),
};
});
}
function resolveRequestBody(variables, schema, operation, opts) {
if (!variables) {
return {};
}
const properties = {};
const required = [];
variables.forEach((variable) => {
if (variable.type.kind === graphql_1.Kind.NON_NULL_TYPE) {
required.push(variable.variable.name.value);
}
properties[variable.variable.name.value] = {
...resolveParamSchema(variable.type, opts),
description: resolveVariableDescription(schema, operation, variable),
};
});
return {
type: 'object',
properties,
...(required.length ? { required } : {}),
};
}
exports.resolveRequestBody = resolveRequestBody;
// array -> [type]
// type -> $ref
// scalar -> swagger primitive
function resolveParamSchema(type, opts) {
if (type.kind === graphql_1.Kind.NON_NULL_TYPE) {
return resolveParamSchema(type.type, opts);
}
if (type.kind === graphql_1.Kind.LIST_TYPE) {
return {
type: 'array',
items: resolveParamSchema(type.type, opts),
};
}
const primitive = (0, utils_1.mapToPrimitive)(type.name.value);
return (primitive ||
opts.customScalars[type.name.value] ||
opts.enumTypes[type.name.value] || { $ref: (0, utils_1.mapToRef)(type.name.value) });
}
exports.resolveParamSchema = resolveParamSchema;
function resolveResponse({ schema, operation, opts, }) {
const operationType = operation.operation;
const rootField = operation.selectionSet.selections[0];
if (rootField.kind === graphql_1.Kind.FIELD) {
if (operationType === 'query') {
const queryType = schema.getQueryType();
const field = queryType.getFields()[rootField.name.value];
return (0, types_1.resolveFieldType)(field.type, opts);
}
if (operationType === 'mutation') {
const mutationType = schema.getMutationType();
const field = mutationType.getFields()[rootField.name.value];
return (0, types_1.resolveFieldType)(field.type, opts);
}
}
}
exports.resolveResponse = resolveResponse;
function isInPath(url, param) {
return url.includes(`:${param}`) || url.includes(`{${param}}`);
}
exports.isInPath = isInPath;
function getOperationFieldNode(schema, operation) {
const selection = operation.selectionSet.selections[0];
const fieldName = selection.name.value;
const typeDefinition = schema.getType((0, title_case_1.titleCase)(operation.operation));
if (!typeDefinition) {
return undefined;
}
const definitionNode = typeDefinition.astNode || (0, graphql_1.parse)((0, graphql_1.printType)(typeDefinition)).definitions[0];
if (!isObjectTypeDefinitionNode(definitionNode)) {
return undefined;
}
return definitionNode.fields.find((field) => field.name.value === fieldName);
}
function resolveDescription(schema, operation) {
const fieldNode = getOperationFieldNode(schema, operation);
return fieldNode?.description?.value || '';
}
function resolveVariableDescription(schema, operation, variable) {
const fieldNode = getOperationFieldNode(schema, operation);
const argument = fieldNode?.arguments?.find((arg) => arg.name.value === variable.variable.name.value);
return argument?.description?.value;
}
exports.resolveVariableDescription = resolveVariableDescription;
function isObjectTypeDefinitionNode(node) {
return node.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION;
}
7 changes: 7 additions & 0 deletions .bob/cjs/src/open-api/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { GraphQLObjectType, GraphQLInputObjectType, GraphQLType } from 'graphql';
export declare function buildSchemaObjectFromType(type: GraphQLObjectType | GraphQLInputObjectType, opts: {
customScalars: Record<string, any>;
}): any;
export declare function resolveFieldType(type: GraphQLType, opts: {
customScalars: Record<string, any>;
}): any;
Loading

0 comments on commit f21c5ad

Please sign in to comment.