Skip to content

const is validated by fjs, add strict mode #579

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

Closed
wants to merge 11 commits into from
Closed

Conversation

Uzlopak
Copy link
Contributor

@Uzlopak Uzlopak commented Jan 20, 2023

This is what I would expect from fast-json-stringify. Probably needs heavy discussion as it is heavily breaking change.

Checklist

@Uzlopak Uzlopak requested review from ivan-tymoshenko and Eomm and removed request for ivan-tymoshenko January 20, 2023 14:20
@ivan-tymoshenko
Copy link
Member

@Uzlopak and everyone else. I finished a discussion with @Eomm on const question, because it's an edge case that shows us how we should design fjs in the future. You can find some thoughts there and join the discussion.
#532 (reply in thread)

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Jan 20, 2023

@mcollina

Before i finish this PR i want to come to a conclusion. I know it is not viable. I would actually add a deep compare function for that.

@ivan-tymoshenko

I know that this is basically the case were we always come to the conclusion on what the purpose of this PR is.

Here i purposefully also used ajv validator as compare function instead the is-my-json-valid, to show that we diverge from the behavior significantly. E.g.

{
  type: 'object',
  properties: {
    foo: { type: 'string', nullable: true, const: 'bar'
  }
}

If we use this schema and use following payload:

{ foo: null }

Then JSON.stringify returns {"foo":null} , ajv throws an error and fast-json-stringify returns {"foo":"bar"}.

Imho fjs has to either return the same value as json.stringify or need to throw an error.

The current behaviour can imho not be correct.

@Eomm
Copy link
Member

Eomm commented Jan 20, 2023

I think we have different visions that could be summarized to:

  1. FJS should validate the input and throw an error if the input doesn't match the json-schema
  2. FJS should NOT validate** the input and should try to serialize it by following the json-schema
  3. FJS should NOT validate** the input and should ignore some json-schema keywords (such as const)

Did I miss some cases?

** even if the produced string output does not match the json-schema

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Jan 23, 2023

Yeah basically.

@Uzlopak Uzlopak force-pushed the const-is-validated branch from 56d087d to f00533a Compare January 24, 2023 04:23
@Uzlopak
Copy link
Contributor Author

Uzlopak commented Jan 24, 2023

WELL!

I wrote a function which based on the const value generates validation code. I oriented myself on fast-deep-equal. Basically it generates branchless code for validating the input.

Maybe this helps to expedite the discussion regarding #532

Copy link
Member

@ivan-tymoshenko ivan-tymoshenko left a comment

Choose a reason for hiding this comment

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

From my point of view trying to validate all types and throwing an error it's the wrong way. You will inevitably run into validation problems. Why do constants throw an error while other keywords don't? I think that we should use constants when it can speed up serialization, and if the value is not equal to a constant we should fallback to default serialization by type.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Jan 24, 2023

I would rather build a serializer which completly validates the input based on the json schema than have a serializer, where I provide a whole schema and in the end only a small part of the schema is actually used. When I write that a field of an input-object has a const value of x, then I expect that it is set accordingly on the input and doesnt get serialized anyway nor how it is done currently with a value which is not set.

@ivan-tymoshenko
Copy link
Member

The problem is that we can't build a serializer + validator without having a significant performance drop. Yes, it might work faster than serializer and validator separately, but it will work much slower than the serializer by itself. Basically, you want to rewrite ajv and merge it with fjs, right?

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Jan 24, 2023

Maybe it makes sense, to design all those issues as "plugins"?

So instead of having here the big discussion regarding what the goal should be, we could have another option validate where we allow to enable or disable this or that json schema keyword?

So if somebody wants to enable maxLength or minLength validation, just set validate.minLength or validate.maxLength accordingly to true.

@ivan-tymoshenko
Copy link
Member

Yes, I'm ok with a solution like that. You can see that in the discussion with @Eomm, we discussed "strict" and "non-strict" versions of fjs. It has something similar with what you suggest. The questions are:

  1. What version should be the default for Fastify?
  2. We need to build the plugin system in such a way that it doesn't turn the code into a mess of conditions. If we make a successful system of dynamically linked plugins, this will be a good solution.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Jan 24, 2023

@ivan-tymoshenko

In #600 I encapsulate the whole context in a variable. If that gets merged, I could instead of in line 859 if (schema.const !== undefined) { do something like if (context.options && (context.options.strict || context.options.validate.const) && schema.const !== undefined) {

t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
t.equal(stringify(input), '{"foo":"bar"}')
t.ok(validate((input)))
Copy link
Member

Choose a reason for hiding this comment

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

we are validating our test code here

Suggested change
t.ok(validate((input)))
t.ok(validate(JSON.parse(output)))


t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
t.throws(() => stringify(input), new Error("The value of '#/properties/foo' does not match schema definition."))
Copy link
Member

Choose a reason for hiding this comment

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

As discussed, I agree to throw behind an option or plugin

in general, my opinion with FJS, it should never throw/validate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if it does not throw, what should it then serialize? just doing JSON.stringify(input) ?

t.equal(output, '{}')
t.ok(validate(JSON.parse(output)), 'valid schema')
t.equal(stringify(input), '{}')
t.ok(validate((input)))
Copy link
Member

Choose a reason for hiding this comment

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

we should validate the fjs output

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Jan 24, 2023

I am currently trying to implement some other keywords like

  • enum
  • maxProperties
  • minProperties
  • maxLength
  • minLength
  • maxItems
  • minItems
  • minimum
  • maximum

Just to get the feeling on how we can implement it properly. Already enum is pain in the ass, because somehow i get an additional } but i dont know why. Lol.
Fixing that and i could push the commit.

But looks good so far.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Feb 8, 2023

I thouht alot about this. I think the biggest issue is, that if we have a big array or object, than the generated validator will be vey big too. So I wonder how ajv solves this issue. But how fast-json-stringify does with const is also very wrong :/

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Feb 23, 2023

ajv basically stores the schema and builds the validator based on the schema. So it is basically ok, what i implemented here. Maybe it makes sense to optimize further for arrays.

@Uzlopak Uzlopak changed the title const is validated by fjs const is validated by fjs, add strict mode Feb 23, 2023
@Uzlopak
Copy link
Contributor Author

Uzlopak commented Feb 23, 2023

By having the context now passed through, it was easy to implement a strict mode.

@ivan-tymoshenko
Copy link
Member

Defining features such as strict mode is an important decision for a library. If you really want to add it, you should think very carefully so as not to create undesirable consequences.

  1. What is a strict mode? What guarantees I can get as a user using strict mode? What checks should it include? What checks should it not include?
  2. The same questions for the unstrict mode.
  3. What mode should be default for the Fastiy?

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Feb 23, 2023

I am not saying that this PR is ready. I am just shuffling the code to determine the next step.

E.g. @Eomm wrote that it we should not throw. But required keyword is throwing. So should we make required non-throwing?

@ivan-tymoshenko
Copy link
Member

It depends on what you want to archive. I don't understand. That is why I'm asking. It's hard to say the correct answer without input conditions. Everyone might understand strict and unstrict modes differently. What are your definitions of strict and unstrict mode?

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Feb 23, 2023

@ivan-tymoshenko
We can even do it like tsconfig and have a global strict setting and e.g. strictRequired, strictConst, strictX.

@Eomm
coercion and strictness... I think coercion is independent of strictness.

@ivan-tymoshenko
Copy link
Member

Ok, I think that idea of configuring rules is a good compromise. I think we should create another issue and discuss it there.

@ivan-tymoshenko
Copy link
Member

Another part of the question is how the unstrict version of FJS should work. When it should throw an error, when it should ignore some input data, etc.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Feb 26, 2023

Any body who wants to work on this further is invited to do so.

But closing it as at the current time there is no eta nor a active discussion.

@Uzlopak Uzlopak closed this Feb 26, 2023
@Eomm
Copy link
Member

Eomm commented Feb 26, 2023

But closing it as at the current time there is no eta nor a active discussion.

Discussion is not a real-time chat, but it could take weeks or months.

I don't want to be the one who says "no" nor stops the progress, but AFAIR, I did not read any proposed plan - did I miss it?

@ivan-tymoshenko
Copy link
Member

A new @Uzlopak idea: incremental validation: #579 (comment). IMHO, this could be a compromise solution. It allows us to have an existing Fastify configuration. The question is how far we want to go with it. I don't really want to reinvent the Ajv.

@simoneb simoneb deleted the const-is-validated branch March 19, 2024 14:23
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 this pull request may close these issues.

5 participants