From ba9ba25a8be5bbd7249c81287a13603b5d7b9e5a Mon Sep 17 00:00:00 2001 From: Atreya <44151328+atreya2011@users.noreply.github.com> Date: Sun, 25 Apr 2021 13:06:35 +0900 Subject: [PATCH] refactor and simplify renderURLSearchParams & flattenObject --- generator/template.go | 134 ++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/generator/template.go b/generator/template.go index 3188d03..7b50412 100644 --- a/generator/template.go +++ b/generator/template.go @@ -196,78 +196,86 @@ function getNotifyEntityArrivalSink(notifyCallback: NotifyStreamEntityArrival }) } +type RequestPayload = Record; +type Primitive = string | boolean | number; + +/** + * Checks if given value is of a primitive type + * @param {unknown} value + * @return {boolean} + */ +function isPrimitive(value: unknown): boolean { + return ( + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ); +} + /** - * Flattens a deeply nested object - * - * @param {Object} deeplyNestedObject + * Flattens a deeply nested request payload and returns an object + * with only primitive values and non-empty array of primitive values + * @param {RequestPayload} requestPayload * @param {String} path - * @return {Record} + * @return {Record} */ -function flattenObject>( - deeplyNestedObject: T, +function flattenRequestPayload( + requestPayload: T, path: string = "" -): T { - return Object.keys(deeplyNestedObject).reduce( +): Record> { + return Object.keys(requestPayload).reduce( (acc: T, key: string): T => { - const value = deeplyNestedObject[key]; - - const newPath = Array.isArray(deeplyNestedObject) - ? path + "." + key - : [path, key].filter(Boolean).join("."); - const isObject = [ - typeof value === "object", - value !== null, - !(value instanceof Date), - !(value instanceof RegExp), - !(Array.isArray(value) && value.length === 0) - ].every(Boolean); - - return isObject - ? { ...acc, ...flattenObject(value as Record, newPath) } - : { ...acc, [newPath]: value }; + const value = requestPayload[key]; + const newPath = path ? [path, key].join(".") : key; + + const isObject = + Object.prototype.toString.call(value) === "[object Object]"; + const isNonEmptyPrimitiveArray = + Array.isArray(value) && + value.every(v => isPrimitive(v)) && + value.length > 0; + + switch (true) { + case isObject: + acc = { + ...acc, + ...flattenRequestPayload(value as RequestPayload, newPath) + }; + break; + case isPrimitive(value) || isNonEmptyPrimitiveArray: + acc = { ...acc, [newPath]: value }; + break; + } + + return acc; }, {} as T - ); + ) as Record>; } /** - * Renders a deeply nested object into a string of URL search parameters - * by first flattening the object and then removing non-primitive array keys, - * non-primitive values and keys which are already present in the URL path. - * @param {Object} deeplyNestedObject - * @param {Array} urlPathParams - * @return {String} + * Renders a deeply nested request payload into a string of URL search + * parameters by first flattening the request payload and then removing keys + * which are already present in the URL path. + * @param {RequestPayload} requestPayload + * @param {string[]} urlPathParams + * @return {string} */ -export function renderURLSearchParams>( - deeplyNestedObject: T, +export function renderURLSearchParams( + requestPayload: T, urlPathParams: string[] = [] ): string { - const flattenedObject = flattenObject(deeplyNestedObject); + const flattenedRequestPayload = flattenRequestPayload(requestPayload); - const urlSearchParams = Object.keys(flattenedObject).reduce( + const urlSearchParams = Object.keys(flattenedRequestPayload).reduce( (acc: string[][], key: string): string[][] => { - const parts = key.split("."); - const index = parts.findIndex(f => Number(f) || f === "0"); - - // if key does not contain only one numeric index as the last element - // then it is an array of objects - if (parts.length === index + 1 || index === -1) { - // remove array index to render url search params properly - // according to http.proto - // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto - const keyWithoutArrayIndex = - index > -1 ? parts.slice(0, index).join(".") : key; - - // values should only be primitive types and key should not be - // present in the url path as a parameter - const value = flattenedObject[key]; - if ( - (typeof value === "string" || - typeof value === "boolean" || - typeof value === "number") && - !urlPathParams.find(f => f === key) - ) { - acc = [...acc, [keyWithoutArrayIndex, value.toString()]]; + // key should not be present in the url path as a parameter + const value = flattenedRequestPayload[key]; + if (!urlPathParams.find(f => f === key)) { + if (Array.isArray(value)) { + acc = [...acc, ...value.map(m => [key, m.toString()])]; + } else { + acc = [...acc, [key, value.toString()]]; } } @@ -276,9 +284,7 @@ export function renderURLSearchParams>( [] as string[][] ); - return urlSearchParams.length > 0 - ? "?" + new URLSearchParams(urlSearchParams).toString() - : ""; + return "?" + new URLSearchParams(urlSearchParams).toString(); } ` @@ -317,7 +323,7 @@ func renderURL(r *registry.Registry) func(method data.Method) string { url := method.URL reg := regexp.MustCompile("{([^}]+)}") matches := reg.FindAllStringSubmatch(url, -1) - fieldList := "[" + fieldsInPath := "[" if len(matches) > 0 { log.Debugf("url matches %v", matches) for _, m := range matches { @@ -325,13 +331,13 @@ func renderURL(r *registry.Registry) func(method data.Method) string { fieldName := fieldNameFn(m[1]) part := fmt.Sprintf(`${req["%s"]}`, fieldName) url = strings.ReplaceAll(url, expToReplace, part) - fieldList += fmt.Sprintf(`"%s", `, fieldName) + fieldsInPath += fmt.Sprintf(`"%s", `, fieldName) } } - fieldList = strings.TrimRight(fieldList, ", ") + "]" + fieldsInPath = strings.TrimRight(fieldsInPath, ", ") + "]" if !method.ClientStreaming && method.HTTPMethod == "GET" { - url += fmt.Sprintf("${fm.renderURLSearchParams(req, %s)}", fieldList) + url += fmt.Sprintf("${fm.renderURLSearchParams(req, %s)}", fieldsInPath) } return url