diff --git a/examples/tests/valid/struct_from_json.w b/examples/tests/valid/struct_from_json.w index 3dbad22a4ef..4e2dded7953 100644 --- a/examples/tests/valid/struct_from_json.w +++ b/examples/tests/valid/struct_from_json.w @@ -265,10 +265,10 @@ let jMyStruct = { val: 10 }, m2: { - data: "10" + val: "10" } }; let myStruct = MyStruct.fromJson(jMyStruct); assert(myStruct.m1.val == 10); -assert(myStruct.m2.data == "10"); \ No newline at end of file +assert(myStruct.m2.val == "10"); \ No newline at end of file diff --git a/examples/tests/valid/subdir/structs_2.w b/examples/tests/valid/subdir/structs_2.w index 76b3e9bb28d..bfe4c0b28c1 100644 --- a/examples/tests/valid/subdir/structs_2.w +++ b/examples/tests/valid/subdir/structs_2.w @@ -1,3 +1,3 @@ struct MyStruct { - data: str; + val: str; } \ No newline at end of file diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index f93c85de7dc..820ee645f57 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -23,7 +23,7 @@ use crate::{ lifts::{Liftable, Lifts}, resolve_super_method, resolve_user_defined_type, symbol_env::SymbolEnv, - ClassLike, Struct, Type, TypeRef, Types, UnsafeRef, VariableKind, CLASS_INFLIGHT_INIT_NAME, + ClassLike, Type, TypeRef, Types, VariableKind, CLASS_INFLIGHT_INIT_NAME, }, visit_context::VisitContext, MACRO_REPLACE_ARGS, MACRO_REPLACE_ARGS_TEXT, MACRO_REPLACE_SELF, WINGSDK_ASSEMBLY_NAME, WINGSDK_RESOURCE, @@ -307,21 +307,21 @@ impl<'a> JSifier<'a> { } } - fn jsify_type(&self, typ: &Type, ctx: &mut JSifyContext) -> Option { + pub fn jsify_type(&self, typ: &Type) -> Option { match typ { Type::Struct(t) => Some(t.name.name.clone()), Type::String => Some("string".to_string()), Type::Number => Some("number".to_string()), Type::Boolean => Some("boolean".to_string()), Type::Array(t) => { - if let Some(inner) = self.jsify_type(&t, ctx) { + if let Some(inner) = self.jsify_type(&t) { Some(format!("{}[]", inner)) } else { None } } Type::Optional(t) => { - if let Some(inner) = self.jsify_type(&t, ctx) { + if let Some(inner) = self.jsify_type(&t) { Some(format!("{}?", inner)) } else { None @@ -653,127 +653,6 @@ impl<'a> JSifier<'a> { } } - pub fn jsify_struct_env_properties(&self, env: &SymbolEnv, ctx: &mut JSifyContext) -> CodeMaker { - let mut code = CodeMaker::default(); - for (field_name, (.., kind)) in env.symbol_map.iter() { - code.line(format!( - "{}: {},", - field_name, - self.jsify_struct_schema_field(&kind.as_variable().unwrap().type_, ctx) - )); - } - code - } - - pub fn jsify_struct_schema_required_fields(&self, env: &SymbolEnv) -> CodeMaker { - let mut code = CodeMaker::default(); - code.open("required: ["); - for (field_name, (_stmt_idx, kind)) in env.symbol_map.iter() { - if !matches!(*kind.as_variable().unwrap().type_, Type::Optional(_)) { - code.line(format!("\"{}\",", field_name)); - } - } - code.close("]"); - code - } - - pub fn jsify_struct_schema_field(&self, typ: &UnsafeRef, ctx: &mut JSifyContext) -> String { - match **typ { - Type::String | Type::Number | Type::Boolean => { - format!("{{ type: \"{}\" }}", self.jsify_type(typ, ctx).unwrap()) - } - Type::Struct(ref s) => { - let mut code = CodeMaker::default(); - code.open("{"); - code.line("type: \"object\","); - code.open("properties: {"); - code.add_code(self.jsify_struct_env_properties(&s.env, ctx)); - code.close("},"); - code.add_code(self.jsify_struct_schema_required_fields(&s.env)); - code.close("}"); - code.to_string().strip_suffix("\n").unwrap().to_string() - } - Type::Array(ref t) | Type::Set(ref t) => { - let mut code = CodeMaker::default(); - code.open("{"); - - code.line("type: \"array\","); - - if matches!(**typ, Type::Set(_)) { - code.line("uniqueItems: true,"); - } - - code.line(format!("items: {}", self.jsify_struct_schema_field(t, ctx))); - - code.close("}"); - code.to_string().strip_suffix("\n").unwrap().to_string() - } - Type::Map(ref t) => { - let mut code = CodeMaker::default(); - code.open("{"); - - code.line("type: \"object\","); - code.line(format!( - "patternProperties: {{ \".*\": {} }}", - self.jsify_struct_schema_field(t, ctx) - )); - - code.close("}"); - code.to_string().strip_suffix("\n").unwrap().to_string() - } - Type::Optional(t) => self.jsify_struct_schema_field(&t, ctx), - Type::Json(_) => "{ type: \"object\" }".to_string(), - _ => "{ type: \"null\" }".to_string(), - } - } - - pub fn jsify_struct(&self, struct_: &Struct, ctx: &mut JSifyContext) -> CodeMaker { - let mut code = CodeMaker::default(); - - code.open("module.exports = function(stdStruct) {".to_string()); - code.open(format!("class {} {{", struct_.name)); - - code.open("static jsonSchema() {".to_string()); - code.open("return {"); - - code.line(format!("id: \"/{}\",", struct_.name)); - code.line("type: \"object\",".to_string()); - - code.open("properties: {"); - - code.add_code(self.jsify_struct_env_properties(&struct_.env, ctx)); - - //close properties - code.close("},"); - - code.add_code(self.jsify_struct_schema_required_fields(&struct_.env)); - - // close return - code.close("}"); - - // close schema - code.close("}"); - - // create _validate() function - code.open("static fromJson(obj) {"); - code.line("return stdStruct._validate(obj, this.jsonSchema())"); - code.close("}"); - - // create _toInflightType function that just requires the generated struct file - code.open("static _toInflightType(context) {".to_string()); - code.line("return `require(\"./${require('path').basename(__filename)}\")(${ context._lift(stdStruct) })`;".to_string()); - code.close("}"); - - // close class - code.close("}"); - // close module.exports - - code.line(format!("return {};", struct_.name.name)); - code.close("};"); - - code - } - // To avoid a performance penalty when evaluating assignments made in the elif statement, // it was necessary to nest the if statements. // diff --git a/libs/wingc/src/struct_schema.rs b/libs/wingc/src/struct_schema.rs index 28900e739be..8c631cf7ba8 100644 --- a/libs/wingc/src/struct_schema.rs +++ b/libs/wingc/src/struct_schema.rs @@ -1,7 +1,7 @@ use crate::{ ast::{Reference, Scope}, jsify::{codemaker::CodeMaker, JSifier, JSifyContext}, - type_check::{is_udt_struct_type, resolve_user_defined_type, Struct}, + type_check::{is_udt_struct_type, resolve_user_defined_type, Struct, symbol_env::SymbolEnv, Type, UnsafeRef}, visit::{self, Visit}, visit_context::VisitContext, }; @@ -20,14 +20,131 @@ impl<'a> StructSchemaVisitor<'a> { } fn jsify_struct(&self, struct_: &Struct) -> CodeMaker { - self.jsify.jsify_struct( - struct_, - &mut JSifyContext { - lifts: None, - visit_ctx: &mut VisitContext::new(), - }, - ) + self.jsify_struct_schema(struct_, self.ctx.current_env().unwrap()) } + + fn jsify_struct_schema_required_fields(&self, env: &SymbolEnv) -> CodeMaker { + let mut code = CodeMaker::default(); + code.open("required: ["); + for (field_name, (_stmt_idx, kind)) in env.symbol_map.iter() { + if !matches!(*kind.as_variable().unwrap().type_, Type::Optional(_)) { + code.line(format!("\"{}\",", field_name)); + } + } + code.close("]"); + code + } + + fn jsify_struct_schema_field(&self, typ: &UnsafeRef, env: &SymbolEnv) -> String { + match **typ { + Type::String | Type::Number | Type::Boolean => { + format!("{{ type: \"{}\" }}", self.jsify.jsify_type(typ).unwrap()) + } + Type::Struct(ref s) => { + let mut code = CodeMaker::default(); + code.open("{"); + code.line("type: \"object\","); + code.open("properties: {"); + code.add_code(self.jsify_struct_env_properties(&s.env)); + code.close("},"); + code.add_code(self.jsify_struct_schema_required_fields(&s.env)); + code.close("}"); + code.to_string().strip_suffix("\n").unwrap().to_string() + } + Type::Array(ref t) | Type::Set(ref t) => { + let mut code = CodeMaker::default(); + code.open("{"); + + code.line("type: \"array\","); + + if matches!(**typ, Type::Set(_)) { + code.line("uniqueItems: true,"); + } + + code.line(format!("items: {}", self.jsify_struct_schema_field(t, env))); + + code.close("}"); + code.to_string().strip_suffix("\n").unwrap().to_string() + } + Type::Map(ref t) => { + let mut code = CodeMaker::default(); + code.open("{"); + + code.line("type: \"object\","); + code.line(format!( + "patternProperties: {{ \".*\": {} }}", + self.jsify_struct_schema_field(t, env) + )); + + code.close("}"); + code.to_string().strip_suffix("\n").unwrap().to_string() + } + Type::Optional(t) => self.jsify_struct_schema_field(&t, env), + Type::Json(_) => "{ type: \"object\" }".to_string(), + _ => "{ type: \"null\" }".to_string(), + } + } + + fn jsify_struct_env_properties(&self, env: &SymbolEnv) -> CodeMaker { + let mut code = CodeMaker::default(); + for (field_name, (.., kind)) in env.symbol_map.iter() { + code.line(format!( + "{}: {},", + field_name, + self.jsify_struct_schema_field(&kind.as_variable().unwrap().type_, env) + )); + } + code + } + + fn jsify_struct_schema(&self, struct_: &Struct, env: &SymbolEnv) -> CodeMaker { + let mut code = CodeMaker::default(); + + code.open("module.exports = function(stdStruct) {".to_string()); + code.open(format!("class {} {{", struct_.name)); + + code.open("static jsonSchema() {".to_string()); + code.open("return {"); + + code.line(format!("id: \"/{}\",", struct_.name)); + code.line("type: \"object\",".to_string()); + + code.open("properties: {"); + + code.add_code(self.jsify_struct_env_properties(&struct_.env)); + + //close properties + code.close("},"); + + code.add_code(self.jsify_struct_schema_required_fields(&struct_.env)); + + // close return + code.close("}"); + + // close schema + code.close("}"); + + // create _validate() function + code.open("static fromJson(obj) {"); + code.line("return stdStruct._validate(obj, this.jsonSchema())"); + code.close("}"); + + // create _toInflightType function that just requires the generated struct file + code.open("static _toInflightType(context) {".to_string()); + code.line(format!( + "return `require(\"./${{require('path').basename(__filename)}}\")(${{ context._lift(stdStruct) }})`;", + )); + code.close("}"); + + // close class + code.close("}"); + // close module.exports + + code.line(format!("return {};", struct_.name.name)); + code.close("};"); + + code + } } // Looks for any references to a struct type, and emits a struct schema file for it. @@ -40,7 +157,9 @@ impl<'a> Visit<'a> for StructSchemaVisitor<'a> { Reference::TypeMember { type_name, .. } => { if is_udt_struct_type(type_name, self.ctx.current_env().unwrap()) { let type_ = resolve_user_defined_type(type_name, self.ctx.current_env().unwrap(), 0); - let struct_code = self.jsify_struct(type_.unwrap().as_struct().unwrap()); + let struct_code = self.jsify_struct( + type_.unwrap().as_struct().unwrap() + ); self.jsify.emit_struct_file( &self.jsify.jsify_user_defined_type( type_name, @@ -66,4 +185,4 @@ impl<'a> Visit<'a> for StructSchemaVisitor<'a> { visit::visit_scope(self, node); self.ctx.pop_env(); } -} +} \ No newline at end of file