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

fix: apply traits to standalone messages from components section #214

Merged

Conversation

c-pius
Copy link
Contributor

@c-pius c-pius commented Dec 10, 2020

Description

@derberg I tried to give it a shot but I am not really sure if the provided function is at the right place and also how the validateAndConvertMessage function works. I included the "x-parser-schema-id":"<anonymous-schema-1>" properties in the output schemas as far as I understood them. Unfortunately, they are only generated for messages that are used in a channel and not the standalone messages. I assume you would know how to fix those problems?

Besided, npm run test-browser was not working on my machine (OSX) from the beginning I cloned the repo. I am on node v12.14.1.

Related issue(s)
Resolves #210

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Welcome to AsyncAPI. Thanks a lot for creating your first pull request.

Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.

Copy link
Member

@magicmatatjahu magicmatatjahu left a comment

Choose a reason for hiding this comment

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

One thing to fix :) Thanks for contribution!

lib/parser.js Show resolved Hide resolved
lib/parser.js Show resolved Hide resolved
@magicmatatjahu
Copy link
Member

I included the "x-parser-schema-id":"" properties in the output schemas as far as I understood them. Unfortunately, they are only generated for messages that are used in a channel and not the standalone messages. I assume you would know how to fix those problems?

The problem is here

constructor(...args) {
As I see we don't traverse in schemas inside the messages in components. We only traverse schemas in channels.message, components.parameters and component.schemas. @derberg should we fix it in this PR or leave it as a followup?

Copy link
Member

@derberg derberg left a comment

Choose a reason for hiding this comment

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

I tried to give it a shot but I am not really sure if the provided function is at the right place and also how the validateAndConvertMessage function works.

It looks good, thanks a bunch for jumping into it 🙇🏼
@magicmatatjahu explained well what is the purpose of this function. I'm only afraid that with the current implementation we will have messages (the ones from components.messages that are referred to channels) that are validated and converted twice, like this one -> https://github.com/asyncapi/parser-js/pull/214/files#diff-b335089706650932f2429d8b648707a16777501ad4e410fcbd35be14900707d8R14 which is a performance issue in my opinion because in large componentized specs with channels the validation will simply be doubled 🤔 I'm opened for suggestions 😄

I included the "x-parser-schema-id":"" properties in the output schemas as far as I understood them. Unfortunately, they are only generated for messages that are used in a channel and not the standalone messages. I assume you would know how to fix those problems?

a simple fix would bring the same issue as in my previous comment, I think. Anonymous schema id goes only to messages in channels. That would be too much for this PR for you to solve, and anyway it is a different topic that requires separate PR. Could you please create a separate issue, please 🙏🏼

I made code change suggestions that you can apply for now.

Besides, npm run test-browser was not working on my machine (OSX) from the beginning I cloned the repo. I am on node v12.14.1.

it was not working because of the missing condition @magicmatatjahu mentioned in one of his comments. Spec used in the browser test is as simple as

asyncapi: 2.0.0
info:
  title: My API
  version: 1.0.0
channels:
  "/test/tester":
    subscribe:
      message: {}

so it covers exactly the same case that @magicmatatjahu described, a spec without messages in components, and in fact, no components object at all. It would be awesome if you could add a test case for such a document to parse_test 🙏🏼

As a side note, I will create a PR to console log errors in the browser test, so nobody has to disable the headless mode of the browser and dig every issue on his/her own


last but not least -> you can take this PR out from draft to enable tests as you are pretty advanced with this PR and we anyway jumped into the review

test/parse_test.js Outdated Show resolved Hide resolved
test/parse_test.js Outdated Show resolved Hide resolved
@c-pius c-pius changed the title fix: [WIP] apply traits to standalone messages fix: apply traits to standalone messages Dec 13, 2020
@c-pius c-pius marked this pull request as ready for review December 13, 2020 15:56
@c-pius
Copy link
Contributor Author

c-pius commented Dec 13, 2020

added the guard that components.messages is defined and an additional test case for when there is no components object.

[duplicate application of traits] is a performance issue in my opinion because in large componentized specs with channels the validation will simply be doubled 🤔 I'm opened for suggestions 😄

I tried to give it a thought but I guess that would require to somehow apply the traits in conjunction with resolving the $ref properties? Maybe also remember the original $ref in a parser extension attribute and if there, replace the message in the operation with the referenced message object where traits have been applied brefore?

@derberg
Copy link
Member

derberg commented Dec 14, 2020

@c-pius trying to combine it with resolving of $ref-s would be quite a refactor and anyway this part is already complicated because of circular refs.

I like your idea about simple remembering that messages were processed already. Just remember it is not just applyTraits that is called twice on messages but validateAndConvertMessage too.

What if we revert the order? like process Messages from components first, and then Channels. Then in the case of applyTraits we do not have to even remember anything, we don't really have a performance issue 🤔 if I'm wrong, blame Monday 😄 but looking at 👇🏼 we are safe because after processing traits, traits prop is removed, so there is no risk it will be processed again? right?

function applyTraits(js) {
  if (Array.isArray(js.traits)) {
    for (const trait of js.traits) {
      for (const key in trait) {
        js[String(key)] = mergePatch(js[String(key)], trait[String(key)]);
      }
    }

    js['x-parser-original-traits'] = js.traits;
    delete js.traits;
  }
}

The only real concern is validateAndConvertMessage. We could add another prop there like x-validated=true and then just check if it is there, and then just not run the validation again. I'm only not sure this is a good prop name, maybe x-origin=components 🤔

But in general, it makes sense right? to process messages from components first, and then just process those unprocessed that are directly under channels?

@c-pius
Copy link
Contributor Author

c-pius commented Dec 14, 2020

Actually I had already tried this using a check if the x-parser-message-name property is already there but only now noticed that this is added at a later point 🤦🏻 So yeah, your suggestion makes sense and would work 👍🏻

Regarding the name of the marker property I don't really mind any. I guess x-validated would be a bit more straightforward and cleaner to handle completely within validateAndConvertMessage. If we use x-origin we would actually need to either set it as part of customMessagesOperations func (therefore spread the logic over two functions, or pass another param to the validateAndConvertMessage indicating the component origin (maybe also possible using the existing pathToPayload param but I think not ideal as well).

I assume you intended it to be x-parser-validated, right? x-validated would have a high probability to collide with user defined extensions on a message I guess.

@derberg
Copy link
Member

derberg commented Dec 15, 2020

@c-pius yeap, x-parser- prefix is a must, sorry

@derberg
Copy link
Member

derberg commented Dec 15, 2020

I'm just afraid about validated as it will kinda imply that other messages from channels are not validated 😄
unless you remove it at the end from the document?

@c-pius
Copy link
Contributor Author

c-pius commented Dec 15, 2020

I'm just afraid about validated as it will kinda imply that other messages from channels are not validated 😄
unless you remove it at the end from the document?

actually I would have put checking and setting the x-parser-validated field both into the validateAndConvertMessage function so that no matter if it is defined in channels or messages, if it has been validated once the flag is set. Probably something like:

async function validateAndConvertMessage(msg, originalAsyncAPIDocument, fileFormat, parsedAsyncAPIDocument, pathToPayload) {
  if (xParserValidated in msg && msg[xParserValidated] === true) {
    //only validate the message if it has not been done before
    return
  }

  //... function logic

  msg[xParserValidated] = true
}

or do you prefer it to be only added for components/messages and removed again afterwards?

@derberg
Copy link
Member

derberg commented Dec 15, 2020

you are right, good approach with having the logic in validateAndConvertMessage

I'm not 100% sure if x-parser-validated should stay or not because I'm not sure what it could be used for if it stays there in the document. I just prefer to leave something that might be useful.

what about x-parser-original-schema-format? every custom parser adds it:

and default parser should do it too -> https://github.com/asyncapi/parser-js/blob/master/lib/asyncapiSchemaFormatParser.js. And it would just have to be clearly described in the readme that every custom parser must add those fields to the document

Thoughts?

@c-pius
Copy link
Contributor Author

c-pius commented Dec 15, 2020

Like the idea. Also similar to the x parser original trait approach then. Updated the PR already. Does that reflect what you meant?

And it would just have to be clearly described in the readme that every custom parser must add those fields to the document

Not sure how to handle this one though? From reading the existing doc I would have assumed to put it in the comment for the getMimeTypes() func but not sure if this would count as "clearly described" 😄

@derberg
Copy link
Member

derberg commented Dec 16, 2020

@c-pius I think adding a comment to parse function in 1st point, between function signature and jsdoc should be sufficient, just a simple explanation of what extra properties must be added to each message by the parser

lib/parser.js Outdated
@@ -190,6 +195,7 @@ async function validateAndConvertMessage(msg, originalAsyncAPIDocument, fileForm
});

msg.schemaFormat = DEFAULT_SCHEMA_FORMAT;
msg[String(xParserOriginalSchemaFormat)] = schemaFormat;
Copy link
Member

Choose a reason for hiding this comment

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

code from https://github.com/asyncapi/parser-js/pull/214/files#diff-2f45270c7389933f71b380538ff52e106ee5cb66a0b573d9967467dfdbc11c02R187 runs a message parser for a given mime type, so if in your AsyncAPI file you have a message that has schema provided in avro, then this code runs -> https://github.com/asyncapi/parser-js/pull/214/files#diff-2f45270c7389933f71b380538ff52e106ee5cb66a0b573d9967467dfdbc11c02R187

  • 1st the avro parser puts avro mime type under x-parser-original-schema-format
  • 2nd you override it with default schema format, asyncapi one, so we loose information about the original schema format

This is why instead of putting this line here, you need to have it in this parser of default asyncapi schema -> https://github.com/asyncapi/parser-js/blob/master/lib/asyncapiSchemaFormatParser.js

Add support for https://github.com/asyncapi/avro-schema-parser/blob/master/index.js#L7 too and put info about it in the readme as well please 🙏🏼

@derberg
Copy link
Member

derberg commented Dec 16, 2020

I also wonder if this knowledge about concerns to performance, and the risk of invoking validation twice is not going to be just our secret here in PR. Not sure though if there is a possible test case that could be added to check this, like check if our default parser was invoked only once? 🤔

const payload = message.payload;
if (!payload) return;

// considered save here because default parser handles JSON Schema payloads
message[String(xParserOriginalPayload)] = JSON.parse(JSON.stringify(payload));
Copy link
Member

Choose a reason for hiding this comment

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

what about 👇🏼

Suggested change
message[String(xParserOriginalPayload)] = JSON.parse(JSON.stringify(payload));
message[String(xParserOriginalPayload)] = Object.assign({}, payload);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Object.assign creates a new object but the copied properties are still shallow copies. so the payload object itself will not get a x-parser-schema-id applied, but for instance a "name" property within the payload will get it applied.

Copy link
Member

Choose a reason for hiding this comment

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

you are right.. 🤔
we cannot leave JSON.stringify as it won't work with circular references in schemas

🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately I am not very deep into the node world (I guess you have noticed already 😄 ). So I would need to let you make the call on what to do best.

Considering that lodash.clonedeep would be available as individual dependency and looking behind the curtains on what would need to be done in an own helper function, I am not sure if manually implementing it is a good idea. On the other hand, I also cannot judge the impact of adding a new dependency to this lib.

Copy link
Member

Choose a reason for hiding this comment

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

lodash is just 3.3kb gziped which is fine, I'm more concerned about performance again. We add something that is not that much needed but will add performance issues to large files again 🤔

I have doubts if this is the way to go. If we should not fall back to the initial idea about adding an additional field that we can use to check if the message was processed or not. Sorry, I just did not take into consideration this issue with copying objects

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no worries, got you. and yeah, since the payload is not actually transformed, the x-parser-original-payload would always include the same structure except the added x-parser attributes. not sure if there is any use for this except consistency with custom parsers regarding the x-parser-original-payload being there?

Copy link
Member

Choose a reason for hiding this comment

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

yeah, the use case if for those that have different formats of schemas, like avro for example, so after parsing the document they can still access the content before conversion in case they want to include it in docs or prefer to read original schema, for example in runtime validations.

so, again back to what could be the name of the extension that holds information if the message was already processed or not 😄

@derberg
Copy link
Member

derberg commented Dec 23, 2020

@c-pius I added a suggestion. Lodash is always an option but for parser let us try to keep it as slim as possible

@c-pius
Copy link
Contributor Author

c-pius commented Dec 24, 2020

so, again back to what could be the name of the extension that holds information if the message was already processed or not 😄

the hardest question of all 😄 as far as I can tell the open options are:

  • we still go for x-parser-original-schema-format
    • pro: reuse of existing property
    • con: we rely on the custom parsers to actually set the property to avoid applying the parsing twice
  • new property which is set and checked in validateAndConvertMessage()
    • pro: independent from parsers, concise within one function
    • con: additional x-parser-... property (e.g. x-parser-message-validated, x-parser-message-parsed, x-parser-message-converted)

I am still wondering actually if we could also set the x-parser-original-schema-format ourselves in validateAndConvertMessage()? Isn't the used parser actually selected by the original schema format || default here:

await PARSERS[String(schemaFormat)]({

If so, we would be independent from the custom parsers and wouldn't need an additional property

@derberg
Copy link
Member

derberg commented Jan 4, 2021

@c-pius hey, sorry mate but I and others had a longer break here. 2020 was crazy busy and we needed a few days without asyncapi.

Let us have a new property -> x-parser-message-parsed

@c-pius
Copy link
Contributor Author

c-pius commented Jan 5, 2021

no worries, I also enjoyed some time off 😉 and happy new year!

I reverted the x-parser-original-schema and x-parser-original-payload content and added the check using x-parser-message-parsed flag in the validateAndConvertMessage() 👍🏻

Copy link
Member

@derberg derberg left a comment

Choose a reason for hiding this comment

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

lgtm, I just made suggestions to be clear about new functionality, that it is only for messages from components.

@magicmatatjahu anything from your side? can you approve?

lib/parser.js Outdated Show resolved Hide resolved
lib/parser.js Outdated Show resolved Hide resolved
lib/parser.js Outdated Show resolved Hide resolved
@derberg derberg changed the title fix: apply traits to standalone messages fix: apply traits to standalone messages from components section Jan 5, 2021
@magicmatatjahu
Copy link
Member

@derberg 8 new lines (without tests etc) with 42 comments? 😄 I'll check, give me some time to read whole thread.

@derberg
Copy link
Member

derberg commented Jan 5, 2021

@magicmatatjahu no worries mate, I don't think you have to read it through, we had a long discussion here as we were just looking for the best possible solution on how to run operations on messages just once) in case the message is in components and also ref-ed from the channel. But we are back to the beginning I would say, just introduced a new extension x-parser-message-parsed and that is it, so I would say just look at the code to make sure I did not overlook something because of our back and forth

magicmatatjahu
magicmatatjahu previously approved these changes Jan 5, 2021
Copy link
Member

@magicmatatjahu magicmatatjahu left a comment

Choose a reason for hiding this comment

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

LGTM with new label :)

await customChannelsOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options);
}

async function validateAndConvertMessage(msg, originalAsyncAPIDocument, fileFormat, parsedAsyncAPIDocument, pathToPayload) {
//check if the message has been parsed before
if (xParserMessageParsed in msg && msg[String(xParserMessageParsed)] === true) return;
Copy link
Member

Choose a reason for hiding this comment

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

It's only a question. (xParserMessageParsed in msg) === true will work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in general yes, if (xParserMessageParsed in msg) would work as well as of now. the second check is to check whether the value of x-parser-message-parsed is also set to boolean value of true. if we omit this we would check the sheer presence of the flag and skip validation. would be okay for me as well. just thought the check for a boolean true value would make it more resilient

Co-authored-by: Lukasz Gornicki <[email protected]>
c-pius and others added 2 commits January 5, 2021 15:01
Co-authored-by: Lukasz Gornicki <[email protected]>
Co-authored-by: Lukasz Gornicki <[email protected]>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 5, 2021

Kudos, SonarCloud 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
0.0% 0.0% Duplication

@c-pius
Copy link
Contributor Author

c-pius commented Jan 5, 2021

@derberg 8 new lines (without tests etc) with 42 comments? 😄 I'll check, give me some time to read whole thread.

well, sorry 😆 just some serious thinking going on here 🤓

@derberg derberg merged commit bb13b9d into asyncapi:master Jan 7, 2021
@asyncapi-bot
Copy link
Contributor

🎉 This PR is included in version 1.3.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@derberg
Copy link
Member

derberg commented Jan 7, 2021

Finally released. Thanks @c-pius for this PR and patience in the discussion 😄

@c-pius
Copy link
Contributor Author

c-pius commented Jan 7, 2021

awesome 🥳

thank you for guiding me and for the great work on this project in general 😄

jonastg added a commit to jonastg/parser-js that referenced this pull request Jan 18, 2021
* master: (24 commits)
  chore: Refactored code location for iterators (asyncapi#225)
  chore(release): 1.3.1 (asyncapi#222)
  fix: apply traits to standalone messages from components section (asyncapi#214)
  test: improve feedback loop from browser tests (asyncapi#216)
  ci: fix the space-issue in bump workflow (asyncapi#218)
  ci: update global workflows (asyncapi#217)
  chore(deps): bump ini from 1.3.5 to 1.3.7 (asyncapi#215)
  ci: rename pr testing job name and test node 14 (asyncapi#213)
  ci: bump workflow to start on push instead of release (asyncapi#212)
  chore(release): 1.3.0 (asyncapi#211)
  feat: add a traverse schema function (asyncapi#198)
  ci: add workflow that bumps parser in other asyncapi repos (asyncapi#208)
  ci: fix release workflow step that is responsible for handling twitter (asyncapi#209)
  ci: update global workflows (asyncapi#206)
  ci: disable any testing on draft PR (asyncapi#204)
  chore(release): 1.2.0 (asyncapi#202)
  feat: extend the components and asyncapi model with has-like functions (asyncapi#192)
  chore(deps-dev): bump semantic-release from 17.0.6 to 17.2.3 (asyncapi#199)
  chore(release): 1.1.1 (asyncapi#197)
  fix: channels with name '/' fail on validation (asyncapi#196)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

messageTrait not applied when message not used in a channel
4 participants