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

Json: schema validation expose schema() object #3790

Closed
hasanaburayyan opened this issue Aug 11, 2023 · 1 comment · Fixed by #4040
Closed

Json: schema validation expose schema() object #3790

hasanaburayyan opened this issue Aug 11, 2023 · 1 comment · Fixed by #4040
Assignees
Labels
🛠️ compiler Compiler good first issue Good for newcomers 📜 lang-spec-impl Appears in the language spec roadmap

Comments

@hasanaburayyan
Copy link
Contributor

The following sections should be added to the language reference:

1.1.4.7 Schemas

Structs have a schema static method which returns a JsonSchema object (P2):

let schema = Contact.schema();
schema.validate(j);
@hasanaburayyan hasanaburayyan added 📜 lang-spec-impl Appears in the language spec roadmap 🛠️ compiler Compiler labels Aug 11, 2023
mergify bot pushed a commit that referenced this issue Aug 12, 2023
The changes in this PR make it possible to call `fromJson` on any compatible struct definition. 

## Implementation notes:
Previously we treated the jsification of structs as a no-op since there is not a JS equivalent. So with this change structs now JSify into a `Struct` file that contains a class with a static `jsonSchema` and a `fromJson` function which will allow for field validation at runtime. 

The schema generated adheres to: https://json-schema.org/understanding-json-schema/

take this simple example Wing code:
```js
struct MyStruct {
	myField: str;
	myOtherField: num;
}
```

this will now generate a JS file named `MyStruct.Struct.js` which looks like this:
```js
module.exports = function(stdStruct, fromInline) {
  class MyStruct {
    static jsonSchema() {
      return {
        id: "/MyStruct",
        type: "object",
        properties: {
          myField: { type: "string" },
          myOtherField: { type: "number" },
        },
        required: [
          "myField",
          "myOtherField",
        ],
        $defs: {
        }
      }
    }
    static fromJson(obj) {
      return stdStruct._validate(obj, this.jsonSchema())
    }
    static _toInflightType(context) {
      return fromInline(`require("./MyStruct.Struct.js")(${ context._lift(stdStruct) })`);
    }
  }
  return MyStruct;
};

```

The piece that brings this all together is the addition of the `Struct` class in our std that only has a `fromJson()` methods at the moment that is a Wing macro. The macro just calls the `fromJson()` method in the generated javascript.

### Misc
We want to stop the user at compile time from calling `fromJson` on a struct that cannot be represented by a Json value ie
```js
struct MyStruct {
	b: cloud.Bucket;
}
let j = {};
MyStruct.fromJson(j);
```

to prevent this I added a check in the typechecker for structs to confirm that if `fromJson` is called that all the fields in the struct are valid for conversion attempt

See image below for error when attempting:
<img width="664" alt="image" src="https://github.com/winglang/wing/assets/45375125/785a2fa6-8823-4fa2-aaa5-4bc8f7ed597f">


Closes: #3653
Closes: #3139

## TODO:
- [x] separate the work done here and the remaining work into different tickets.
- [x] update language reference

## Followup issues that are out of scope for this PR:
- #3792
- #3790
- #3789
- #3788

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] Docs updated (only required for features)
- [x] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
@staycoolcall911 staycoolcall911 added the good first issue Good for newcomers label Aug 20, 2023
@hasanaburayyan hasanaburayyan self-assigned this Sep 2, 2023
@mergify mergify bot closed this as completed in #4040 Sep 8, 2023
mergify bot pushed a commit that referenced this issue Sep 8, 2023
## Summary
So this is actually quite a substantial refactor to how we generate jsonschemas for structs. Previously anytime we encountered a user defined wing struct we generated a json schema file with a schema and fromJson methods. However this meant that we were not created schema files for structs defined outside of the wing code (I.E. JSII imported). With the new changes I am taking a lazy creation approach where we only generate schemas for structs if there are references that would require the schema to exist.

This change also no longer generates a separate file for the schema, instead it creates an instance of a stdlib.std.StructSchema using the jsonschema inline.

The other big change is the removal of the require statements and defs from schemas that were dependent on other schemas. As in take for example this Wing struct:
```js
struct A {
  val: num;
}

struct B {
  data: str;
}

struct C extends A {
  b: B;
}
```


The struct schema for `C` used to look like this:
```js
{
        id: "/C",
        type: "object",
        properties: {
          ...require("./A.Struct.js")().jsonSchema().properties,
          b: { "$ref": "#/$defs/B" },
        },
        required: [
          "b",
          ...require("./A.Struct.js")().jsonSchema().required,
        ],
        $defs: {
          "B": { type: "object", "properties": require("./B.Struct.js")().jsonSchema().properties },
          ...require("./A.Struct.js")().jsonSchema().$defs,
        }
      }
```

Now it looks like this:
```js
{
        id: "/C",
        type: "object",
        properties: {
          b: {
            type: "object",
            properties: {
              data: { type: "string" },
            },
            required: [
              "data",
            ]
          },
          val: { type: "number" },
        },
        required: [
          "b",
          "val",
        ]
      }
```

### Implementation Notes
1. Lazy creation of schemas, only emit struct schema files if there exists a reference to the struct type in the code.
2. Schemas no longer consist of require statements for dependent schemas. As in the schemas are now fully self contained.
3. Pre-Jsification parse of the ast to emit struct files for referenced schemas.

Closes: #3943
Closes: #3790

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] Docs updated (only required for features)
- [x] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
@monadabot
Copy link
Contributor

Congrats! 🚀 This was released in Wing 0.29.9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🛠️ compiler Compiler good first issue Good for newcomers 📜 lang-spec-impl Appears in the language spec roadmap
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

3 participants