Skip to content

Commit

Permalink
fix(compiler): cannot use fromJson on jsii imported struct (#4040)
Browse files Browse the repository at this point in the history
## 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)*.
  • Loading branch information
hasanaburayyan authored Sep 8, 2023
1 parent eaba03a commit a8e0ecb
Show file tree
Hide file tree
Showing 22 changed files with 609 additions and 1,295 deletions.
67 changes: 67 additions & 0 deletions docs/docs/04-standard-library/02-std/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,64 @@ to get values from.



### JsonSchema <a name="JsonSchema" id="@winglang/sdk.std.JsonSchema"></a>

Struct Schema.

#### Initializers <a name="Initializers" id="@winglang/sdk.std.JsonSchema.Initializer"></a>

```wing
new JsonSchema(schema: Json);
```

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@winglang/sdk.std.JsonSchema.Initializer.parameter.schema">schema</a></code> | <code><a href="#@winglang/sdk.std.Json">Json</a></code> | *No description.* |

---

##### `schema`<sup>Required</sup> <a name="schema" id="@winglang/sdk.std.JsonSchema.Initializer.parameter.schema"></a>

- *Type:* <a href="#@winglang/sdk.std.Json">Json</a>

---

#### Methods <a name="Methods" id="Methods"></a>

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@winglang/sdk.std.JsonSchema.asStr">asStr</a></code> | Retrieve the json schema as a string. |
| <code><a href="#@winglang/sdk.std.JsonSchema.validate">validate</a></code> | Attempt to validate a json object against the schema. |

---

##### `asStr` <a name="asStr" id="@winglang/sdk.std.JsonSchema.asStr"></a>

```wing
asStr(): str
```

Retrieve the json schema as a string.

##### `validate` <a name="validate" id="@winglang/sdk.std.JsonSchema.validate"></a>

```wing
validate(obj: Json): void
```

Attempt to validate a json object against the schema.

###### `obj`<sup>Required</sup> <a name="obj" id="@winglang/sdk.std.JsonSchema.validate.parameter.obj"></a>

- *Type:* <a href="#@winglang/sdk.std.Json">Json</a>

the Json object to validate.

---




### Map <a name="Map" id="@winglang/sdk.std.Map"></a>

Immutable Map.
Expand Down Expand Up @@ -2645,6 +2703,7 @@ Shared behavior for all structs.
| **Name** | **Description** |
| --- | --- |
| <code><a href="#@winglang/sdk.std.Struct.fromJson">fromJson</a></code> | Converts a Json to a Struct. |
| <code><a href="#@winglang/sdk.std.Struct.schema">schema</a></code> | Retrieve the schema for this struct. |
| <code><a href="#@winglang/sdk.std.Struct.tryFromJson">tryFromJson</a></code> | Converts a Json to a Struct, returning nil if the Json is not valid. |

---
Expand All @@ -2663,6 +2722,14 @@ Converts a Json to a Struct.

---

##### `schema` <a name="schema" id="@winglang/sdk.std.Struct.schema"></a>

```wing
Struct.schema();
```

Retrieve the schema for this struct.

##### `tryFromJson` <a name="tryFromJson" id="@winglang/sdk.std.Struct.tryFromJson"></a>

```wing
Expand Down
52 changes: 47 additions & 5 deletions examples/tests/valid/struct_from_json.w
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// TODO: https://github.com/winglang/wing/issues/3792
// bring cloud;
// let j = { public: false };
// let x = cloud.BucketProps.fromJson(j);
bring cloud;

// JSII structs
let j = { public: false };
let x = cloud.BucketProps.fromJson(j);
assert(x.public == false);

test "inflight jsii struct conversion" {
let x = cloud.BucketProps.fromJson(j);
assert(x.public == false);
}

// simple case
struct Foo {
Expand Down Expand Up @@ -248,4 +255,39 @@ let jj1 = {
};

let externalBar = externalStructs.MyOtherStruct.fromJson(jj1);
assert(externalBar.data.val == 10);
assert(externalBar.data.val == 10);

// test namespaced struct collisions dont occur
bring "./subdir/structs_2.w" as otherExternalStructs;

struct MyStruct {
m1: externalStructs.MyStruct;
m2: otherExternalStructs.MyStruct;
}

let jMyStruct = {
m1: {
val: 10
},
m2: {
val: "10"
}
};

let myStruct = MyStruct.fromJson(jMyStruct);
assert(myStruct.m1.val == 10);
assert(myStruct.m2.val == "10");

// Test using schema object
let schema = MyStruct.schema();
schema.validate(jMyStruct); // Should not throw exception

let expectedSchema = {"id":"/MyStruct","type":"object","properties":{"m1":{"type":"object","properties":{"val":{"type":"number"}},"required":["val"]},"m2":{"type":"object","properties":{"val":{"type":"string"}},"required":["val"]}},"required":["m1","m2"]};

assert(schema.asStr() == Json.stringify(expectedSchema));

test "inflight schema usage" {
let s = MyStruct.schema();
s.validate(jMyStruct);
assert(schema.asStr() == Json.stringify(expectedSchema));
}
3 changes: 3 additions & 0 deletions examples/tests/valid/subdir/structs_2.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
struct MyStruct {
val: str;
}
Loading

0 comments on commit a8e0ecb

Please sign in to comment.