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: schema changes to support SumType #1322

Merged
merged 15 commits into from
Apr 24, 2024
Merged

feat: schema changes to support SumType #1322

merged 15 commits into from
Apr 24, 2024

Conversation

deniseli
Copy link
Contributor

@deniseli deniseli requested a review from a team as a code owner April 23, 2024 20:46
@deniseli deniseli requested review from matt2e and removed request for a team April 23, 2024 20:46
@alecthomas alecthomas mentioned this pull request Apr 23, 2024
Pos: posToProto(s.Pos),
Comments: s.Comments,
Name: s.Name,
Variants: variants,
Copy link
Contributor

Choose a reason for hiding this comment

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

we have a helper for this, so you don't have to construct your own list!

Suggested change
Variants: variants,
Variants: nodeListToProto[*schemapb.Type](s.Variants),

Copy link
Contributor

Choose a reason for hiding this comment

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

oops, actually:

Suggested change
Variants: variants,
Variants: slices.Map(s.Variants, typeToProto),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done :D

for i, v := range node.Variants {
variants[i] = jsonschema.SchemaOrBool{TypeObject: nodeToJSSchema(v, refs)}
}
return &jsonschema.Schema{OneOf: variants}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice!

Though, this should be using {"kind": xyz, "value": ...}, or is that in a followup?

Copy link
Contributor

Choose a reason for hiding this comment

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

fyi - from feedback during the design sync we actually lifted the original struct out of value, so it'll just be an additional field named @_kind (prefixed with @_ to avoid conflicts) at the top level now

Copy link
Collaborator

@alecthomas alecthomas Apr 23, 2024

Choose a reason for hiding this comment

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

Mmm, @_kind can still conflict though if the sum type variant is a map:

sumtype SumType = {String: String} | Int

var value SumType = {"@_kind": "foo"}

Copy link
Contributor

Choose a reason for hiding this comment

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

ah good point, i misunderstood your prior comment from the design sync. we can go with the kind/value approach then

Copy link
Collaborator

@alecthomas alecthomas left a comment

Choose a reason for hiding this comment

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

LGTM!

@@ -41,6 +41,7 @@ var (
{Name: "String", Pattern: `"(?:\\.|[^"])*"`},
{Name: "Number", Pattern: `[0-9]+(?:\.[0-9]+)?`},
{Name: "Punct", Pattern: `[%/\-\_:[\]{}<>()*+?.,\\^$|#~!\'@]`},
{Name: "Equals", Pattern: `=`},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just add this to Punct instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

Pos Position `parser:"" protobuf:"1,optional"`

Comments []string `parser:"@Comment*" protobuf:"2"`
Name string `parser:"'sumtype' @Ident Equals" protobuf:"3"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be just '=' by adding = to Punct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oo lala, thank you!

"type": "string"
}
},
"type": "integer"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this is valid is it? Unless I'm misunderstand this schema a type of "integer" can't have properties.

Copy link
Collaborator

@matt2e matt2e left a comment

Choose a reason for hiding this comment

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

approved (pending a decision on where we land on the wrapped + kind vs embeded + @_kind thing)

@worstell worstell self-requested a review April 24, 2024 00:41
@alecthomas
Copy link
Collaborator

I think there's a JSON schema validation round trip test that we should use here to verify it's working as intended.

@deniseli
Copy link
Contributor Author

deniseli commented Apr 24, 2024

Hey folks! Posting a summary here to round this out:

  • Overall, things here look good, except we aren't sure what we want the jsonschema output to look like. Specifically, we aren't sure what the shape of it should be, nor how kind will be inserted into the output schema w.r.t. the oneof.
  • The current approach just adds a separate Properties map adjacent to the variants' types. We aren't sure if this is ultimately what we'll want, but the unit tests in jsonschema_test.go have been updated to verify that sumtype refs work as intended. I stepped through TestJSONSchemaValidation per Alec's suggestion above and inspected the schema and interface at each step. I can at least say that the code works the way we intend; we just don't know if what we intend is actually the right thing.
  • The output produced by this change does pass the sniff test in https://www.jsonschemavalidator.net/. Also, when we run ftl dev ./examples/go --recreate, the console does show time and echo correctly. So at least, this shouldn't break things (that I know of).
  • This PR does not add any go-runtime changes yet. We will likely need to revisit the jsonschema structure for sumtypes when we get to that, if not sooner.
  • Since this PR is massive and touches a lot of the backend code, we want to go ahead and merge instead of sitting on it and accumulating endless merge conflicts. I will do that now.
  • If anything breaks as a result of this, please let me know. Thanks!

@deniseli deniseli merged commit c214d92 into main Apr 24, 2024
11 checks passed
@deniseli deniseli deleted the dli/sumtypes-decl branch April 24, 2024 18:55
@alecthomas
Copy link
Collaborator

I don't see a test here exercising this... ie. there's no input value anywhere (I can see?) with @_kind in it?

@@ -307,7 +332,8 @@ func TestInvalidEnumValidation(t *testing.T) {
"any": [{"name": "Name"}, "string", 1, 1.23, true, "2018-11-13T20:20:39+00:00", ["one"], {"one": 2}, null],
"keyValue": {"key": "string", "value": 1},
"stringEnumRef": "B",
"intEnumRef": 3
"intEnumRef": 3,
"sumTypeRef": 10
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where's the @_kind?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes I see it in the schema, but it's not used in the value.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Two cases where I don't think this will approach will work:

  1. How do we differentiate between a sum type with Float and Int without a kind?
  2. How does @_kind work if the type being discriminated is a map that can contain @_kind as a key.?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahhhhh ok this finally make sense to me now. I'll think about this a bit more and try out whatever I come up with with the rest of the types to make sure we can discriminate.

@worstell
Copy link
Contributor

Hey folks! Posting a summary here to round this out:

  • Overall, things here look good, except we aren't sure what we want the jsonschema output to look like. Specifically, we aren't sure what the shape of it should be, nor how kind will be inserted into the output schema w.r.t. the oneof.
  • The current approach just adds a separate Properties map adjacent to the variants' types. We aren't sure if this is ultimately what we'll want, but the unit tests in jsonschema_test.go have been updated to verify that sumtype refs work as intended. I stepped through TestJSONSchemaValidation per Alec's suggestion above and inspected the schema and interface at each step. I can at least say that the code works the way we intend; we just don't know if what we intend is actually the right thing.
  • The output produced by this change does pass the sniff test in https://www.jsonschemavalidator.net/. Also, when we run ftl dev ./examples/go --recreate, the console does show time and echo correctly. So at least, this shouldn't break things (that I know of).
  • This PR does not add any go-runtime changes yet. We will likely need to revisit the jsonschema structure for sumtypes when we get to that, if not sooner.
  • Since this PR is massive and touches a lot of the backend code, we want to go ahead and merge instead of sitting on it and accumulating endless merge conflicts. I will do that now.
  • If anything breaks as a result of this, please let me know. Thanks!

btw, as far as I'm aware jsonschema is only used for the console, so wouldn't expect it to break anything in the lifecycle processes.

we won't need to worry about the issue of representing the kind property on non-struct types with the modified approach—wrapping in a struct that contains fields "kind" and "value", where the original value is nested in "value". let's update in a follow up

@alecthomas
Copy link
Collaborator

Agreed @worstell, but maybe let's have a quick chat about it in the daily sync? The kind+value combo is robust, but also won't help model a lot of real world use cases. I'm thinking in particular of the very common case where JSON schemas support something like "foo" | ["foo", "bar"], "foo" | {"name": "foo", "field": "bar"}, etc. OTOH if String | [String] is extended with String | [String] | [Int] at some future point, it will break :(

Maybe we support bare sum types if there's no ambiguity, and the kind discriminator when there is, and it's a backwards incompatible change that we warn about if switching from one to the other...?

@worstell
Copy link
Contributor

@alecthomas sounds good, let's chat in sync!

making sum types enums could be another solution here, though it introduces a bunch of other complexity (worth it if we determine this to be the right approach conceptually, though). If we structured them as enums, each variant would have a name, a type, and possibly a "raw value" (inspired by your Swift reference). the value of kind is the variant name, rather than its type (so maybe we rename kind to name or something else).

in Go, all variants are necessarily either type aliases or themselves structs. the alias or struct name will become the variant name. in other runtimes we can structure the approach to ensure we have some way of deriving variant "names".

ex:
type Scalar string in user code, ftl/echo.Scalar key in the Scheme, and FTL schema variant name Scalar. Can be identified on ingress with the enum name, Scalar. it would look something like this in the schema:

module echo {
  enum ScalarOrList {
     Scalar String
     List   [String]
  }
}

deniseli added a commit that referenced this pull request Apr 29, 2024
Reverts everything in #1322
except for the addition of `=` to `Punct` in `backend/schema/parser.go`,
which we will still be using for value enums.

Going forward, sumtypes will be implemented as type enums.
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.

4 participants