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

"." in method docs example will break rendering #1286

Closed
2 of 4 tasks
pquerner opened this issue Jul 20, 2022 · 10 comments · Fixed by #1338
Closed
2 of 4 tasks

"." in method docs example will break rendering #1286

pquerner opened this issue Jul 20, 2022 · 10 comments · Fixed by #1338

Comments

@pquerner
Copy link

I have a endpoint which consumes a JSON body with a optional callback_url property, which is of type string. If this property is set, we will HTTP POST to this URL.

Setting up examples with

    /**
    * Get status for order id
    *
    * @param orderId
    * @isString orderId
    * @param body Accepts external callback url<br>
    * which will receive the response from our API as POST request<br><br>
    * Currently only accessible URLs are tested. If your URL needs authentication, please contact us.
    * @example body {
    *  "session": "example",
    *  "callback_url": "https:\/\/external.url.com\/api\/post\/stuff\/here"
    * }
    * @example body {
    *  "session": "example"
    * }
    */

I have also tried the URL string unescaped, or without slashes (except for the protocol).
A string such as a will render it just fine, so I suspect something with the URL string itself, although I am not sure which part is responsible (protocol, slash escapes, ...)

Sorting

  • I'm submitting a ...

    • bug report
    • feature request
    • support request
  • I confirm that I

    • used the search to make sure that a similar issue hasn't already been submit

Expected Behavior

Example 1 should be displayed / generated without problems.

Current Behavior

Example is generated as
image

while missing the header Example 1 and the actual example. When changing the selectbox option to any other type, and back to the Example 1 (empty string), this is shown:

image

Using a string for callback_url such as a will output this:

image

Code:

    * @example body {
    *  "session": "example",
    *  "callback_url": "a"
    * }

Context (Environment)

Version of the library: 4.1.0
Version of NodeJS: v18.6.0

Swagger yml generated:

{
	"components": {
		"examples": {},
		"headers": {},
		"parameters": {},
		"requestBodies": {},
		"responses": {},
		"schemas": {
			"RequestError": {
				"properties": {
					"success": {
						"type": "boolean",
						"enum": [
							false
						],
						"nullable": false
					},
					"error": {
						"type": "number",
						"enum": [
							500
						],
						"nullable": false
					},
					"details": {
						"properties": {},
						"additionalProperties": {
							"properties": {
								"value": {
									"type": "string"
								},
								"message": {
									"type": "string"
								}
							},
							"type": "object"
						},
						"type": "object"
					}
				},
				"required": [
					"success",
					"error"
				],
				"type": "object",
				"additionalProperties": false
			},
			"UnauthorizedError": {
				"properties": {
					"success": {
						"type": "boolean",
						"enum": [
							false
						],
						"nullable": false
					},
					"error": {
						"type": "number",
						"enum": [
							401
						],
						"nullable": false
					}
				},
				"required": [
					"success",
					"error"
				],
				"type": "object",
				"additionalProperties": false
			},
			"InternalError": {
				"properties": {
					"success": {
						"type": "boolean",
						"enum": [
							false
						],
						"nullable": false
					},
					"error": {
						"type": "number",
						"enum": [
							500
						],
						"nullable": false
					}
				},
				"required": [
					"success",
					"error"
				],
				"type": "object",
				"additionalProperties": false
			},
			"StatusGetRequest": {
				"properties": {
					"session": {
						"type": "string"
					},
					"callback_url": {
						"type": "string"
					}
				},
				"required": [
					"session"
				],
				"type": "object",
				"additionalProperties": false
			}
		},
		"securitySchemes": {
			"api_key": {
				"type": "apiKey",
				"name": "x-api-key",
				"in": "header"
			}
		}
	},
	"info": {
		"title": "xxx",
		"version": "1.0.0",
		"contact": {
			"name": "xx ",
			"url": "xxx"
		}
	},
	"openapi": "3.0.0",
	"paths": {
		"/status/get/order/{orderId}": {
			"post": {
				"operationId": "GetStatusForOrder",
				"responses": {
					"200": {
						"description": "",
						"content": {
							"application/json": {
								"schema": {}
							}
						}
					},
					"400": {
						"description": "",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/RequestError"
								}
							}
						}
					},
					"401": {
						"description": "",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/UnauthorizedError"
								}
							}
						}
					},
					"500": {
						"description": "",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/InternalError"
								}
							}
						}
					}
				},
				"description": "Get status for order id",
				"security": [
					{
						"api_key": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "orderId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				],
				"requestBody": {
					"description": "Accepts external callback url<br>\nwhich will receive the response from our API as POST request<br><br>\nCurrently only accessible URLs are tested. If your URL needs authentication, please contact us.",
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/StatusGetRequest",
								"description": "Accepts external callback url<br>\nwhich will receive the response from our API as POST request<br><br>\nCurrently only accessible URLs are tested. If your URL needs authentication, please contact us."
							},
							"examples": {
								"": {
									"value": {
										"session": "example",
										"callback_url": "https://external.url.com/api/post/stuff/here"
									}
								},
								"Example 1": {
									"value": {
										"session": "example"
									}
								}
							}
						}
					}
				}
			}
		}
	},
	"servers": [
		{
			"url": "/"
		}
	]
}

This part of the example is empty string:

"examples": {
								"": {
									"value": {
										"session": "example",
										"callback_url": "https://external.url.com/api/post/stuff/here"
									}
								},
								"Example 1": {
									"value": {
										"session": "example"
									}
								}
							}
@github-actions
Copy link

Hello there pquerner 👋

Thank you for opening your very first issue in this project.

We will try to get back to you as soon as we can.👀

@pquerner
Copy link
Author

I think I tracked it down to not "URLs" but points ".".

ie, this fails:

@example couponCode "ABC.DEF"
@example couponCode "https://google.com"

and this doesnt fail

@example couponCode "ABCDEF" 
@example couponCode "https://googlecom"

I have also tested this behaviour against the latest version 4.1.1.

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days

@github-actions github-actions bot added the Stale label Aug 28, 2022
@pquerner pquerner changed the title Example title not rendered with URL "." in method docs example will break rendering Aug 31, 2022
@pquerner
Copy link
Author

I am guessing a faulty regexp somewhere (interpret "." literally?), but I couldnt find any in the library. Probably in some external, but I had no luck there either.

@WoH
Copy link
Collaborator

WoH commented Aug 31, 2022

We mostly rely on the AST that TS provides, there is a similar issue.
https://github.com/lukeautry/tsoa/blob/master/packages/cli/src/utils/jsDocUtils.ts

If you wanna debug it, start around here and see where it goes off the rails

@github-actions github-actions bot removed the Stale label Sep 1, 2022
@pquerner
Copy link
Author

pquerner commented Sep 14, 2022

I believe its this code:

https://github.com/lukeautry/tsoa/blob/v4.1.2/packages/cli/src/metadataGeneration/parameterGenerator.ts#L354-L360

      const isExample = (tag.tagName.text === 'example' || tag.tagName.escapedText === 'example') && !!tag.comment && comment?.startsWith(parameterName);
      const hasExampleLabel = (comment?.indexOf('.') || -1) > 0;

      if (isExample) {
        // custom example label is delimited by first '.' and the rest will all be included as example label
        exampleLabels.push(hasExampleLabel ? comment?.split(' ')[0].split('.').slice(1).join('.') : undefined);
      }
    /**
     * Some docs
     *
     * @example body {
     *  "session": "example",
     *  "callback_url": "https://googlecom"
     * }
     * @example body {
     *  "session": "example"
     * }
     */

produces

isExample: true,
hasExampleLabel: false,
comment: 'body {"session": "example","callback_url": "https://googlecom"}',
exampleLabels: "[null]"

while

/**
     * Some docs
     *
     * @example body {
     *  "session": "example",
     *  "callback_url": "https://google.com"
     * }
     * @example body {
     *  "session": "example"
     * }
     */

produces

isExample: true,
hasExampleLabel: true,
comment: 'body {"session": "example","callback_url": "https://google.com"}',
exampleLabels: [""]

An empty string is pushed into the exampleLabels array, while the example without the dot pushes undefined into it > resulting in a "Example <Counter>" output and the other a "<empty string>" output.

Maybe something like this can be used for request examples

@example body {
 "session": "example",
 "callback_url": "https://googlecom"
} (Test)

where Test would become the example label

or better yet have a proper RequestExample decorator so it doesnt have to parse the doc comments ?

Feature request was already seen in #1107

@pquerner
Copy link
Author

pquerner commented Sep 14, 2022

Ok this was a tough nut to crack and I don't believe its documented anywhere (but in the code).

This works:

/**
     * Some docs
     *
     * @example body.Test {
     *  "session": "example",
     *  "callback_url": "https://google.com"
     * }
     * @example body {
     *  "session": "example"
     * }
     */

image

This honestly needs to be made simpler, no? :D

// Edit
While this breaks again:

/**
     * Some docs
     *
     * @example body.Test {
     *  "session": "pe.ter",
     *  "callback_url": "https://google.com"
     * }
     * @example body {
     *  "session": "example"
     * }
     * @example body {
     *  "session": "asd.f"
     * }
     */
Error: Minified React error #185; visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

(when its just asdf its fine again)..

🤷

This also works

/**
     * Some docs
     *
     * @example body.Test {
     *  "session": "pe.ter",
     *  "callback_url": "https://google.com"
     * }
     * @example body.Test2 {
     *  "session": "example"
     * }
     * @example body.Test3 {
     *  "session": "asd.f"
     * }
     */

Idk.. its time for a proper RequestExample :D this is insane

//Edit
This also doesnt like spaces as example label. The response example labels can do that aswell.

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days

@github-actions github-actions bot added the Stale label Oct 15, 2022
@pquerner
Copy link
Author

pquerner commented Oct 18, 2022

I'd like to work on this. Hopefully I dont get stuck, but I am sure WoH will be here to help me out if needed? :)

//edit

OK I need a little push right from the start. I dont even know where to properly start.
I thought I could fetch some knowledge from https://github.com/lukeautry/tsoa/pull/1309/files or https://github.com/lukeautry/tsoa/pull/1123/files
but I dont know where the "generator" stuff happens (for the swagger.json).

So far I only have this decorator which I can import in a test project, but it doesnt actually do anything (of course).

I thought I could even watch older git commits of smaller stuff, but even that seems out of reach since they are too old and the projects way shifted away from this "style".

I wanted the decorator to behave like that:

interface ISessionRequest {
    session: string
}

@RequestExample({
        "session": "example"
    }, 'body', 'Label')
public async debug(
        @Body() body: ISessionRequest
    ): Promise<IResponse> {
        return Promise.resolve({
            foo: 'bar'
        });
    }

Thought of linking the interface into that aswell? Like Example decorator

@RequestExample<ISessionRequest>({
        "session": "asd"
    }, 'Label')

Which would make the "body" thingy obsolete I reckon.

@davidqqq
Copy link
Contributor

davidqqq commented Nov 5, 2022

Indeed this was caused by a bad hasExampleLabel check. I have opened a PR to fix this with your example as test cases. The hasExampleLabel now will only check if there is an example and only look at the dot in location part but not in the content.

@WoH WoH closed this as completed in #1338 Nov 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants