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

feat(example-validation-app): add validation example #4895

Merged
merged 2 commits into from
Mar 23, 2020
Merged

Conversation

dhmlau
Copy link
Member

@dhmlau dhmlau commented Mar 16, 2020

Related to: #4874

Add a validation example.
Next step:

Files changed apart from the generated files:

  • src/models/coffee-shop.model.ts: validation logic
  • src/interceptors/validate-phone-num.interceptor.ts: validation logic
  • src/contollers/coffee-shop.controller.ts: use of class level interceptor
  • src/tests/acceptance/validate.acceptance.ts

Checklist

👉 Read and sign the CLA (Contributor License Agreement) 👈

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

👉 Check out how to submit a PR 👈

@dhmlau
Copy link
Member Author

dhmlau commented Mar 18, 2020

The coverage decrease reported by coverall are in examples/hello-world and examples/rpc-server packages which I didn't make any changes.


The application will start on port 3000. Open http://localhost:3000/explorer in
your browser. You can try to test the validation for the `/coffee-shops`
endpoints.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please point to the document you will be completing in this PR : #4874 . This way, the user won't be frustrated. We shouldn't expect them to dig into the acceptance and unit tests to figure out what steps they should take to test the validation in the API Explorer.

Great example, by the way :)

Copy link
Contributor

@jannyHou jannyHou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 Good to have the json spec + interceptor's validation example, mostly LGTM. A few suggestions and minor comments.

examples/validation-app/src/migrate.ts Show resolved Hide resolved
*/
@bind({tags: {key: ValidatePhoneNumInterceptor.BINDING_KEY}})
export class ValidatePhoneNumInterceptor implements Provider<Interceptor> {
static readonly BINDING_KEY = `interceptors.${ValidatePhoneNumInterceptor.name}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I think usually we create a keys.ts file to expose the bingding keys. It's easy for organizing, especially when turn it into a component.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's generated by lb4 interceptor.

) {
// Add pre-invocation logic here
const coffeeShop: CoffeeShop = new CoffeeShop();
if (invocationCtx.methodName === 'create')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, how about using method level interceptor to generate the instance?
I think the situation is similar to what I did in the access control example's method level voter: before executing the validation/authorization logic, we need do some pre-work based on method.

And I am a bit confused why have to create a new model instance first? Wouldn't the data from request already a model instance?

(Considering this example aims to demo validation. The interceptor details doesn't can be improved in a 2nd round :p)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emonddr had the same question about instantiating the CoffeeShop instance, but I couldn't find a good alternatives. See discussion: https://github.com/strongloop/loopback-next/pull/4874/files#r392316159. Please let me know if you have a better solution! Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For method level interceptor, I'm thinking the interceptor should be applied to multiple methods in the CoffeeShopController (create, update, updateById, patch), so that's why i'm using class level interceptor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still apply the decorator at relevant methods. That might be more explicit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dhmlau The method level interceptor can be separated from the global one, e.g. create method has an endpoint level interceptor which adds the coffeeshop instance to the invocationContext, and the controller level interceptor does the validation.

I am good with the current implementation to land this PR, we can always discuss and improve after it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked to @jannyHou. She was thinking to have the class level interceptor to validate the instance and retrieve the instance based on the method name in the method level interceptor. But it's good with my current implementation.

examples/validation-app/README.md Outdated Show resolved Hide resolved
examples/validation-app/README.md Show resolved Hide resolved
@@ -28,7 +28,7 @@ import {ValidatePhoneNumInterceptor} from '../interceptors';
import {CoffeeShop} from '../models';
import {CoffeeShopRepository} from '../repositories';

// Add this line to apply interceptor to this class
// Add this line to apply interceptor to this method
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You apply the decorator at class level and use if logic in the implementation to skip other methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to say the comment should be // Add this line to apply interceptor to this class

@dhmlau
Copy link
Member Author

dhmlau commented Mar 19, 2020

@jannyHou @raymondfeng, I've got the "undefined" code working in the interceptor. Please review my latest commit on the changes: 1cecd21.

@emonddr, I've modified the README to contain more info on how to trigger the validation error.

@Arathy-sivan
Copy link

When I used this code I got the error ,,

Server is running at http://[::1]:3000
Try http://[::1]:3000/ping
Unhandled error in POST /phonemds: 500 TypeError: Cannot convert undefined or null to object
at Function.assign ()
at ValidatePhoneNumInterceptor.intercept (D:\phone\src\interceptors\validateph.interceptor.ts:47:14)
at D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:175:14
at Object.transformValueOrPromise (D:\phone\node_modules@loopback\context\src\value-promise.ts:270:12)
at GenericInterceptorChain.invokeNextInterceptor (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:170:12)
at GenericInterceptorChain.next (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:158:17)
at GenericInterceptorChain.invokeInterceptors (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:144:17)
at Object.invokeInterceptors (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:207:16)
at D:\phone\node_modules@loopback\context\src\interceptor.ts:343:14
at Object.tryWithFinally (D:\phone\node_modules@loopback\context\src\value-promise.ts:196:14)
at Object.invokeMethodWithInterceptors (D:\phone\node_modules@loopback\context\src\interceptor.ts:337:10)
at Object.invokeMethod (D:\phone\node_modules@loopback\context\src\invocation.ts:184:10)
at ControllerRoute.invokeHandler (D:\phone\node_modules@loopback\rest\src\router\controller-route.ts:145:12)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at MySequence.handle (D:\phone\src\sequence.ts:29:22)
at HttpHandler._handleRequest (D:\phone\node_modules@loopback\rest\src\http-handler.ts:78:5)

// Add pre-invocation logic here
let coffeeShop: CoffeeShop | undefined;
if (invocationCtx.methodName === 'create')
Object.assign(coffeeShop, invocationCtx.args[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work? coffeeShop is undefined at this point. Won't the Object.assign fail if LHS is undefined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right. it failed when i'm running the actual app. I've reverted it back to the original:

    const coffeeShop: CoffeeShop = new CoffeeShop();

If you have a better solution, please let me know!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. Just had webex with Dom, and we have a solution. Updated the PR. Thanks!

npm start
```

The application will start on port 3000. Open http://localhost:3000/explorer in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start on -> listen on?

@dhmlau
Copy link
Member Author

dhmlau commented Mar 20, 2020

@Arathy-sivan, thanks for bringing it up. I've fixed it in the latest commit. Please check again. Thanks.

examples/validation-app/README.md Outdated Show resolved Hide resolved
examples/validation-app/README.md Outdated Show resolved Hide resolved
@@ -0,0 +1,57 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is generated automatically, but if it's not needed for the example, can it be removed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean to remove copyrights for all files in this example?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no sorry, I meant the ping controller.

@Arathy-sivan
Copy link

@Arathy-sivan, thanks for bringing it up. I've fixed it in the latest commit. Please check again. Thanks.

Thanks, it's working.

@Arathy-sivan
Copy link

Arathy-sivan commented Mar 21, 2020

Hi,
I want to use a field 'Landphoneno' and i wish to use this as an optional,
ie; @Property({required: false}). I used the above code and executed without using the field 'Landphoneno' then i got an error ,

Unhandled error in POST /phonemds: 500 TypeError: Cannot read property 'slice' of undefined
at ValidatePhoneNumInterceptor.isAreaCodeValid (D:\phone\src\interceptors\validateph.interceptor.ts:62:35)
at ValidatePhoneNumInterceptor.intercept (D:\phone\src\interceptors\validateph.interceptor.ts:46:13)
at D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:175:14
at Object.transformValueOrPromise (D:\phone\node_modules@loopback\context\src\value-promise.ts:270:12)
at GenericInterceptorChain.invokeNextInterceptor (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:170:12)
at GenericInterceptorChain.next (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:158:17)
at GenericInterceptorChain.invokeInterceptors (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:144:17)
at Object.invokeInterceptors (D:\phone\node_modules@loopback\context\src\interceptor-chain.ts:207:16)
at D:\phone\node_modules@loopback\context\src\interceptor.ts:343:14
at Object.tryWithFinally (D:\phone\node_modules@loopback\context\src\value-promise.ts:196:14)
at Object.invokeMethodWithInterceptors (D:\phone\node_modules@loopback\context\src\interceptor.ts:337:10)
at Object.invokeMethod (D:\phone\node_modules@loopback\context\src\invocation.ts:184:10)
at ControllerRoute.invokeHandler (D:\phone\node_modules@loopback\rest\src\router\controller-route.ts:145:12)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at MySequence.handle (D:\phone\src\sequence.ts:29:22)
at HttpHandler._handleRequest (D:\phone\node_modules@loopback\rest\src\http-handler.ts:78:5)

@achrinza
Copy link
Member

@Ararathy-sivan Cannot read property “slice” of undefined hints that the interceptor may not have code to handle cases where the property is optional.

Ensure that your interceptor is coded to handle this case.

Copy link
Contributor

@nabdelgadir nabdelgadir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job 👍


const url = app.restServer.url;
console.log(`Server is running at ${url}`);
console.log(`Try ${url}/ping`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this line too

@dhmlau dhmlau merged commit c7a5cf7 into master Mar 23, 2020
@dhmlau dhmlau deleted the validation-example branch March 23, 2020 20:18
@dhmlau
Copy link
Member Author

dhmlau commented Mar 23, 2020

FYI - The decrease in coverage complained by coverall are from other examples which I didn't change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants