Skip to content

TS: use camel case in object field names #41

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
124 changes: 112 additions & 12 deletions generators/tsclient/testdata/todo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ class ClientError extends Error {
}

/**
* Call method with params via a POST request.
* Call method with body via a POST request.
*/

async function call(url: string, method: string, authToken?: string, params?: any): Promise<string> {
async function call(url: string, method: string, authToken?: string, body?: string): Promise<string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json'
}
Expand All @@ -35,7 +35,7 @@ async function call(url: string, method: string, authToken?: string, params?: an

const res = await fetch(url + '/' + method, {
method: 'POST',
body: JSON.stringify(params),
body: body,
headers
})

Expand Down Expand Up @@ -77,21 +77,121 @@ export class Client {
}

/**
* Decoder is used as the reviver parameter when decoding responses.
* Decode the response to an object.
*/

private decoder(key: any, value: any) {
return typeof value == 'string' && reISO8601.test(value)
? new Date(value)
: value
private decodeResponse(res: string): any {
const obj = JSON.parse(res)

const isObject = (val: any) =>
val && typeof val === "object" && val.constructor === Object
const isDate = (val: any) =>
typeof val == "string" && reISO8601.test(val)
const isArray = (val: any) => Array.isArray(val)

const decode = (val: any): any => {
let ret: any

if (isObject(val)) {
ret = {}
for (const prop in val) {
if (!Object.prototype.hasOwnProperty.call(val, prop)) {
continue
}
ret[this.toCamelCase(prop)] = decode(val[prop])
}
} else if (isArray(val)) {
ret = []
val.forEach((item: any) => {
ret.push(decode(item))
})
} else if (isDate(val)) {
ret = new Date(val)
} else {
ret = val
}

return ret
}

return decode(obj)
}

/**
* Convert a field name from snake case to camel case.
*/

private toCamelCase(str: string): string {
const capitalize = (str: string) =>
str.charAt(0).toUpperCase() + str.slice(1)

const tok = str.split("_")
let ret = tok[0]
tok.slice(1).forEach((t) => (ret += capitalize(t)))

return ret
}

/**
* Encode the request object.
*/

private encodeRequest(obj: any): string {
const isObject = (val: any) =>
val && typeof val === "object" && val.constructor === Object
const isArray = (val: any) => Array.isArray(val)

const encode = (val: any): any => {
let ret: any

if (isObject(val)) {
ret = {}
for (const prop in val) {
if (!Object.prototype.hasOwnProperty.call(val, prop)) {
continue
}
ret[this.toSnakeCase(prop)] = encode(val[prop])
}
} else if (isArray(val)) {
ret = []
val.forEach((item: any) => {
ret.push(encode(item))
})
} else {
ret = val
}

return ret
}

return JSON.stringify(encode(obj))
}

/**
* Convert a field name from camel case to snake case.
*/

private toSnakeCase(str: string): string {
let ret = ""
const isUpper = (c: string) => !(c.toLowerCase() == c)

for (let c of str) {
if (isUpper(c)) {
ret += "_" + c.toLowerCase()
} else {
ret += c
}
}

return ret
}

/**
* addItem: adds an item to the list.
*/

async addItem(params: AddItemInput) {
await call(this.url, 'add_item', this.authToken, params)
await call(this.url, 'add_item', this.authToken, this.encodeRequest(params))
}

/**
Expand All @@ -100,7 +200,7 @@ export class Client {

async getItems(): Promise<GetItemsOutput> {
let res = await call(this.url, 'get_items', this.authToken)
let out: GetItemsOutput = JSON.parse(res, this.decoder)
let out: GetItemsOutput = this.decodeResponse(res)
return out
}

Expand All @@ -109,8 +209,8 @@ export class Client {
*/

async removeItem(params: RemoveItemInput): Promise<RemoveItemOutput> {
let res = await call(this.url, 'remove_item', this.authToken, params)
let out: RemoveItemOutput = JSON.parse(res, this.decoder)
let res = await call(this.url, 'remove_item', this.authToken, this.encodeRequest(params))
let out: RemoveItemOutput = this.decodeResponse(res)
return out
}

Expand Down
122 changes: 111 additions & 11 deletions generators/tsclient/tsclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ class ClientError extends Error {
}

/**
* Call method with params via a POST request.
* Call method with body via a POST request.
*/

async function call(url: string, method: string, authToken?: string, params?: any): Promise<string> {
async function call(url: string, method: string, authToken?: string, body?: string): Promise<string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json'
}
Expand All @@ -46,7 +46,7 @@ async function call(url: string, method: string, authToken?: string, params?: an

const res = await fetch(url + '/' + method, {
method: 'POST',
body: JSON.stringify(params),
body: body,
headers
})

Expand Down Expand Up @@ -94,13 +94,113 @@ func Generate(w io.Writer, s *schema.Schema, fetchLibrary string) error {
out(w, " }\n")
out(w, "\n")
out(w, " /**\n")
out(w, " * Decoder is used as the reviver parameter when decoding responses.\n")
out(w, " * Decode the response to an object.\n")
out(w, " */\n")
out(w, "\n")
out(w, " private decoder(key: any, value: any) {\n")
out(w, " return typeof value == 'string' && reISO8601.test(value)\n")
out(w, " ? new Date(value)\n")
out(w, " : value\n")
out(w, " private decodeResponse(res: string): any {\n")
out(w, " const obj = JSON.parse(res)\n")
out(w, "\n")
out(w, " const isObject = (val: any) =>\n")
out(w, " val && typeof val === \"object\" && val.constructor === Object\n")
out(w, " const isDate = (val: any) =>\n")
out(w, " typeof val == \"string\" && reISO8601.test(val)\n")
out(w, " const isArray = (val: any) => Array.isArray(val)\n")
out(w, "\n")
out(w, " const decode = (val: any): any => {\n")
out(w, " let ret: any\n")
out(w, "\n")
out(w, " if (isObject(val)) {\n")
out(w, " ret = {}\n")
out(w, " for (const prop in val) {\n")
out(w, " if (!Object.prototype.hasOwnProperty.call(val, prop)) {\n")
out(w, " continue\n")
out(w, " }\n")
out(w, " ret[this.toCamelCase(prop)] = decode(val[prop])\n")
out(w, " }\n")
out(w, " } else if (isArray(val)) {\n")
out(w, " ret = []\n")
out(w, " val.forEach((item: any) => {\n")
out(w, " ret.push(decode(item))\n")
out(w, " })\n")
out(w, " } else if (isDate(val)) {\n")
out(w, " ret = new Date(val)\n")
out(w, " } else {\n")
out(w, " ret = val\n")
out(w, " }\n")
out(w, "\n")
out(w, " return ret\n")
out(w, " }\n")
out(w, "\n")
out(w, " return decode(obj)\n")
out(w, " }\n")
out(w, "\n")
out(w, " /**\n")
out(w, " * Convert a field name from snake case to camel case.\n")
out(w, " */\n")
out(w, "\n")
out(w, " private toCamelCase(str: string): string {\n")
out(w, " const capitalize = (str: string) =>\n")
out(w, " str.charAt(0).toUpperCase() + str.slice(1)\n")
out(w, "\n")
out(w, " const tok = str.split(\"_\")\n")
out(w, " let ret = tok[0]\n")
out(w, " tok.slice(1).forEach((t) => (ret += capitalize(t)))\n")
out(w, "\n")
out(w, " return ret\n")
out(w, " }\n")
out(w, "\n")
out(w, " /**\n")
out(w, " * Encode the request object.\n")
out(w, " */\n")
out(w, "\n")
out(w, " private encodeRequest(obj: any): string {\n")
out(w, " const isObject = (val: any) =>\n")
out(w, " val && typeof val === \"object\" && val.constructor === Object\n")
out(w, " const isArray = (val: any) => Array.isArray(val)\n")
out(w, "\n")
out(w, " const encode = (val: any): any => {\n")
out(w, " let ret: any\n")
out(w, "\n")
out(w, " if (isObject(val)) {\n")
out(w, " ret = {}\n")
out(w, " for (const prop in val) {\n")
out(w, " if (!Object.prototype.hasOwnProperty.call(val, prop)) {\n")
out(w, " continue\n")
out(w, " }\n")
out(w, " ret[this.toSnakeCase(prop)] = encode(val[prop])\n")
out(w, " }\n")
out(w, " } else if (isArray(val)) {\n")
out(w, " ret = []\n")
out(w, " val.forEach((item: any) => {\n")
out(w, " ret.push(encode(item))\n")
out(w, " })\n")
out(w, " } else {\n")
out(w, " ret = val\n")
out(w, " }\n")
out(w, "\n")
out(w, " return ret\n")
out(w, " }\n")
out(w, "\n")
out(w, " return JSON.stringify(encode(obj))\n")
out(w, " }\n")
out(w, "\n")
out(w, " /**\n")
out(w, " * Convert a field name from camel case to snake case.\n")
out(w, " */\n")
out(w, "\n")
out(w, " private toSnakeCase(str: string): string {\n")
out(w, " let ret = \"\"\n")
out(w, " const isUpper = (c: string) => !(c.toLowerCase() == c)\n")
out(w, "\n")
out(w, " for (let c of str) {\n")
out(w, " if (isUpper(c)) {\n")
out(w, " ret += \"_\" + c.toLowerCase()\n")
out(w, " } else {\n")
out(w, " ret += c\n")
out(w, " }\n")
out(w, " }\n")
out(w, "\n")
out(w, " return ret\n")
out(w, " }\n")
out(w, "\n")

Expand Down Expand Up @@ -130,16 +230,16 @@ func Generate(w io.Writer, s *schema.Schema, fetchLibrary string) error {
out(w, " let res = ")
// call
if len(m.Inputs) > 0 {
out(w, "await call(this.url, '%s', this.authToken, params)\n", m.Name)
out(w, "await call(this.url, '%s', this.authToken, this.encodeRequest(params))\n", m.Name)
} else {
out(w, "await call(this.url, '%s', this.authToken)\n", m.Name)
}
out(w, " let out: %sOutput = JSON.parse(res, this.decoder)\n", format.GoName(m.Name))
out(w, " let out: %sOutput = this.decodeResponse(res)\n", format.GoName(m.Name))
out(w, " return out\n")
} else {
// call
if len(m.Inputs) > 0 {
out(w, " await call(this.url, '%s', this.authToken, params)\n", m.Name)
out(w, " await call(this.url, '%s', this.authToken, this.encodeRequest(params))\n", m.Name)
} else {
out(w, " await call(this.url, '%s', this.authToken)\n", m.Name)
}
Expand Down
4 changes: 2 additions & 2 deletions generators/tstypes/testdata/todo_types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Item is a to-do item.
export interface Item {
// created_at is the time the to-do item was created.
created_at?: Date
// createdAt is the time the to-do item was created.
createdAt?: Date

// id is the id of the item. This field is read-only.
id?: number
Expand Down
7 changes: 4 additions & 3 deletions generators/tstypes/tstypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ func writeFields(w io.Writer, s *schema.Schema, fields []schema.Field) {

// writeField to writer.
func writeField(w io.Writer, s *schema.Schema, f schema.Field) {
fmt.Fprintf(w, " // %s is %s%s\n", f.Name, f.Description, schemautil.FormatExtra(f))
name := format.JsName(f.Name)
fmt.Fprintf(w, " // %s is %s%s\n", name, f.Description, schemautil.FormatExtra(f))
if f.Required {
fmt.Fprintf(w, " %s: %s\n", f.Name, jsType(s, f))
fmt.Fprintf(w, " %s: %s\n", name, jsType(s, f))
} else {
fmt.Fprintf(w, " %s?: %s\n", f.Name, jsType(s, f))
fmt.Fprintf(w, " %s?: %s\n", name, jsType(s, f))
}
}

Expand Down