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

refactor: add extensions method to the bindings #587

Conversation

magicmatatjahu
Copy link
Member

@magicmatatjahu magicmatatjahu commented Aug 31, 2022

Description

  • Add missed extensions method to the Bindings model
  • Fix types for AsyncAPISchemaObject
  • Fix types for Collection abstract model
  • Add unit tests

Related issue(s)
Part of #481
Part of #482

@magicmatatjahu magicmatatjahu added the enhancement New feature or request label Aug 31, 2022
@magicmatatjahu magicmatatjahu force-pushed the next/fix-collections-of-bindings branch from 1dbe848 to db1217e Compare September 1, 2022 14:01
@magicmatatjahu magicmatatjahu marked this pull request as ready for review September 1, 2022 14:47
src/models/v2/message.ts Outdated Show resolved Hide resolved
@magicmatatjahu
Copy link
Member Author

@smoya Ok, I applied all suggestions. Could you check again?

@magicmatatjahu magicmatatjahu force-pushed the next/fix-collections-of-bindings branch from 66581e1 to b497904 Compare September 6, 2022 07:29
package-lock.json Outdated Show resolved Hide resolved
@magicmatatjahu magicmatatjahu requested a review from smoya September 6, 2022 07:40
json<K extends keyof J>(key: K): J[K];
json(key?: keyof J) {
if (key === undefined) return this._json;
if (key === undefined || typeof this._json !== 'object') return this._json;
Copy link
Member

Choose a reason for hiding this comment

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

I don't get why we need this change. what type could be other than object? If the user provides a key, it is expected to only return a value.

Copy link
Member Author

Choose a reason for hiding this comment

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

The value can also be different from the object - we have one case in our spec, schema can have a boolean, true or false value.

Copy link
Member

Choose a reason for hiding this comment

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

But what if the user provides a value for key? You should still assume the user wants the value for such a key, no matters the type of this._json.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, I will fix 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.

Done, new function body:

    if (key === undefined) return this._json;
    if (this._json === null || this._json === undefined) return this._json;
    return this._json[key];

@magicmatatjahu magicmatatjahu requested a review from smoya September 6, 2022 08:47
abstract has(id: string): boolean;
get(id: string): T | undefined {
const possibleItem = this._meta.originalData?.[id];
return typeof possibleItem === 'undefined' ? this.__get(id) : possibleItem;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be just the opposite? First we see if __get is declared, if so, means there is a desire from the subclass to override the whole get?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, but have in mind that __get here is treated as fallback to retrieve data when originalData (which can be omitted) doesn't exist or given key also doesn't exist. With your solution we need to duplicate const possibleItem = this._meta.originalData?.[id]; logic in every __get function.

Copy link
Member

Choose a reason for hiding this comment

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

What I suggest is that we do the opposite; something like:

const possibleItem = this.__get(id);
return typeof possibleItem === 'undefined' ? this._meta.originalData?.[id] : possibleItem;

Copy link
Member Author

Choose a reason for hiding this comment

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

But by this you will decrease optimization, I made looking for object first to improve read speed. Also originalData is optional so we can pass objects to the collections constructor only when we will have 100% sure that keys in objects === ids in collections.

Copy link
Member

@smoya smoya Sep 6, 2022

Choose a reason for hiding this comment

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

Correct me if I'm wrong, please. Imagine having the following document:

components:
  messages:
    test1:
      messageId: 'test'
    test2:
      messageId: 'test1'

What .components().messages().get('test1') should return?
I expect messageId should have preference over the key, so I would say the returned message would be:

test2:
      messageId: 'test1'

However, with your change, that won't happen since this._meta.originalData?.[id] will match with the other message.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can remove that if you want, because atm we only use it in the extensions case for bindings. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

I'm concerned about the Developer (of the parser) Experience, not the final user.
I understand the benefit; kind of o(1) VS o(n) lookups, However I'm always in favor of not introducing early optimizations in code, and this one might be one, especially if not executed thinking on DX.

As a last effort to keep it, maybe at least renaming the originalData var to indexedCollection or similar and adding a comment on what this is about could help.

Just to clarify, this could also be used for example for servers and channels (v2) right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I opt for originalData because in bindings model we need to pass original data (not changed), because then we need to read from original bindings field all extensions for extensions() method. indexedCollection indicates that this data is transformed.

Just to clarify, this could also be used for example for servers and channels (v2) right?

Yeah, also for bindings and extensions models - in every collections where we change object to array 1:1 - we change key to the id of element.

I will remove __get, but we need originalData meta field.

Copy link
Member

@smoya smoya Sep 6, 2022

Choose a reason for hiding this comment

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

indexedCollection doesn't indicate that data is transformed but just indexed by something (we can assume by id), which can be perfectly the original data.

Copy link
Member Author

@magicmatatjahu magicmatatjahu Sep 6, 2022

Choose a reason for hiding this comment

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

Yeah, but we can even transform to the object original data - for example messages we can transform to the object, where keys will be messageIds, so we need originalData and probably indexedCollection.

Ok, I removed the __get function. We will think about optimization after release.

@@ -1,7 +1,7 @@
import type { BaseModel } from "./base";

export interface ExtensionInterface extends BaseModel {
export interface ExtensionInterface<T = any> extends BaseModel {
Copy link
Member

Choose a reason for hiding this comment

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

Would T be ever any other type rather than any ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Extension can include any value, primitive, object, array.

Copy link
Member Author

Choose a reason for hiding this comment

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

From slack:

You can have any value in extension, we cannot say what exactly type is in extension, but user can cast extension value to given type, so he/she can do this:

const extension = model.extensions().get<TwitterExtensionsShape>('twitter');

and then:

extensions.value()

will return

TwitterExtensionsShape

type.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah. Makes totally sense. Thanks for clarifying!

@sonarqubecloud
Copy link

sonarqubecloud bot commented Sep 6, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@magicmatatjahu magicmatatjahu requested a review from smoya September 6, 2022 10:20
Copy link
Member

@smoya smoya left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀

@magicmatatjahu
Copy link
Member Author

/rtm

@asyncapi-bot asyncapi-bot merged commit 3db1d09 into asyncapi:next-major Sep 6, 2022
@magicmatatjahu magicmatatjahu deleted the next/fix-collections-of-bindings branch September 6, 2022 10:25
magicmatatjahu added a commit to magicmatatjahu/parser-js that referenced this pull request Oct 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request ready-to-merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants