From 4efacd46668071a5e360674712371692a8a5aa67 Mon Sep 17 00:00:00 2001 From: ylides Date: Wed, 30 Aug 2023 05:30:49 +0900 Subject: [PATCH 1/9] add serde to dependencies --- Cargo.lock | 2 ++ type-generator/Cargo.toml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0d385d8..94ab4d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,8 @@ dependencies = [ "once_cell", "proc-macro2", "quote", + "serde", + "serde_json", "structopt", "swc_common", "swc_ecma_ast", diff --git a/type-generator/Cargo.toml b/type-generator/Cargo.toml index dddb3b1..c959e8b 100644 --- a/type-generator/Cargo.toml +++ b/type-generator/Cargo.toml @@ -14,6 +14,8 @@ default = ["serde"] serde = [] [dependencies] +serde = { version = "1.0.183", features = ["derive"] } +serde_json = "1.0.105" proc-macro2 = "1.0.66" quote = "1.0.33" structopt = "0.3.26" From 84cc9fde381f0c4d30fc5ad5ee12ba1c8f6536ce Mon Sep 17 00:00:00 2001 From: ylides Date: Wed, 30 Aug 2023 05:31:31 +0900 Subject: [PATCH 2/9] wip: deser jsonschema --- type-generator/src/frontend.rs | 1 + type-generator/src/frontend/syntax.rs | 855 ++++++++++++++++++++++++++ 2 files changed, 856 insertions(+) create mode 100644 type-generator/src/frontend/syntax.rs diff --git a/type-generator/src/frontend.rs b/type-generator/src/frontend.rs index 2867c54..283308c 100644 --- a/type-generator/src/frontend.rs +++ b/type-generator/src/frontend.rs @@ -1,5 +1,6 @@ pub mod merge_union_type_lits; pub mod name_types; +pub mod syntax; use once_cell::sync::Lazy; use std::{borrow::Cow, collections::HashMap}; diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs new file mode 100644 index 0000000..6e49f27 --- /dev/null +++ b/type-generator/src/frontend/syntax.rs @@ -0,0 +1,855 @@ +use std::{collections::HashMap, marker::PhantomData}; + +use serde::__private::de::Content; +use serde::de::{self, EnumAccess, MapAccess, SeqAccess}; +use serde::{ + de::{DeserializeSeed, Visitor}, + Deserialize, Deserializer, +}; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JsonSchemaRoot<'a> { + #[serde(flatten)] + _schema: SchemaVersion, + #[serde(borrow)] + pub definitions: HashMap<&'a str, Property<'a>>, + pub one_of: Vec>, +} + +#[derive(Debug, Deserialize)] +struct SchemaVersion { + #[serde(rename = "$schema", with = "schema_version")] + _schema: (), +} + +mod schema_version { + use serde::{self, Deserialize, Deserializer}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> + where + D: Deserializer<'de>, + { + let s = <&'de str>::deserialize(deserializer)?; + if s == "http://json-schema.org/draft-07/schema#" + || s == "http://json-schema.org/draft-07/schema" + { + Ok(()) + } else { + Err(serde::de::Error::custom(format!( + "unsupported schema version: {s}" + ))) + } + } +} + +#[derive(Debug)] +pub enum Property<'a> { + Ref(RefProperty<'a>), + Value(TypedValue<'a>), + Enum(EnumProperty<'a>), +} + +#[derive(Debug, Deserialize)] +pub struct RefProperty<'a> { + #[serde(rename = "$ref")] + pub _ref: &'a str, +} + +#[derive(Debug, Deserialize)] +pub struct StringProperty { + pub format: StringFormat, +} + +#[derive(Debug, Deserialize)] +pub enum StringFormat { + #[serde(rename = "date-time")] + DateTime, + #[serde(rename = "uri")] + Uri, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ObjectProperty<'a> { + pub properties: HashMap<&'a str, Property<'a>>, + pub required: Option>, + pub title: Option<&'a str>, + pub additional_properties: bool, +} + +#[derive(Debug, Deserialize)] +pub struct ArrayProperty<'a> { + #[serde(borrow = "'a")] + pub items: Box>, +} + +#[derive(Debug)] +pub struct EnumProperty<'a> { + pub members: EnumMembers<'a>, +} + +#[derive(Debug)] +pub enum TypedValue<'a> { + Null, + Other { + is_nullable: bool, + value: NonNullValue<'a>, + }, +} + +#[derive(Debug)] +pub enum NonNullValue<'a> { + Boolean, + Integer, + Number, + Object(ObjectProperty<'a>), + Array(ArrayProperty<'a>), + String(StringProperty), +} + +#[derive(Debug)] +pub enum EnumMembers<'a> { + Str { + contains_null: bool, + members: Vec<&'a str>, + }, + Boolean(Vec), +} + +struct PropertyVisitor<'a> { + value: PhantomData>, +} + +impl<'a> PropertyVisitor<'a> { + fn new() -> Self { + Self { value: PhantomData } + } +} + +impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let (tag, content) = deserializer.deserialize_any(PropertyVisitor::new())?; + let deserializer = serde::__private::de::ContentDeserializer::::new(content); + match tag { + Tag::Ref => RefProperty::deserialize(deserializer).map(Property::Ref), + Tag::Type(ty) => { + let (ty, is_nullable) = match ty { + PropertyType::Null => return Ok(Property::Value(TypedValue::Null)), + PropertyType::NonNull(ty) => (ty, false), + PropertyType::Nullable(ty) => (ty, true), + }; + match ty { + NonNullPropertyType::Boolean => Ok(Property::Value(TypedValue::Other { + is_nullable, + value: NonNullValue::Boolean, + })), + NonNullPropertyType::Integer => Ok(Property::Value(TypedValue::Other { + is_nullable, + value: NonNullValue::Integer, + })), + NonNullPropertyType::Number => Ok(Property::Value(TypedValue::Other { + is_nullable, + value: NonNullValue::Number, + })), + NonNullPropertyType::String => { + StringProperty::deserialize(deserializer).map(|c| { + Property::Value(TypedValue::Other { + is_nullable, + value: NonNullValue::String(c), + }) + }) + } + NonNullPropertyType::Object => { + ObjectProperty::deserialize(deserializer).map(|c| { + Property::Value(TypedValue::Other { + is_nullable, + value: NonNullValue::Object(c), + }) + }) + } + NonNullPropertyType::Array => { + ArrayProperty::deserialize(deserializer).map(|c| { + Property::Value(TypedValue::Other { + is_nullable, + value: NonNullValue::Array(c), + }) + }) + } + } + } + Tag::Enum(ty) => { + struct StringEnum<'a, const NULLABLE: bool> { + value: Vec<&'a str>, + } + struct StringEnumVisitor<'a, const NULLABLE: bool> { + value: PhantomData>, + } + impl<'a, const NULLABLE: bool> StringEnumVisitor<'a, NULLABLE> { + fn new() -> Self { + Self { value: PhantomData } + } + } + impl<'de: 'a, 'a, const NULLABLE: bool> Visitor<'de> for StringEnumVisitor<'a, NULLABLE> { + type Value = StringEnum<'a, NULLABLE>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + if NULLABLE { + formatter.write_str("a nullable string enum") + } else { + formatter.write_str("a non-null string enum") + } + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut contains_null = false; + let mut vec = + Vec::<&'a str>::with_capacity(seq.size_hint().unwrap_or_default()); + if NULLABLE { + if let Some(s) = seq.next_element::>()? { + if let Some(s) = s { + vec.push(s); + } else { + contains_null = true; + } + } + } else { + while let Some(s) = seq.next_element::<&'a str>()? { + vec.push(s); + } + } + assert_eq!(NULLABLE, contains_null); + Ok(StringEnum { value: vec }) + } + } + + impl<'de: 'a, 'a, const NULLABLE: bool> Deserialize<'de> for StringEnum<'a, NULLABLE> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(StringEnumVisitor::::new()) + } + } + match ty { + PropertyType::NonNull(NonNullPropertyType::Boolean) => { + #[derive(Deserialize)] + struct BooleanEnum { + #[serde(rename = "enum")] + _enum: Vec, + } + BooleanEnum::deserialize(deserializer).map(|c| { + Property::Enum(EnumProperty { + members: EnumMembers::Boolean(c._enum), + }) + }) + } + PropertyType::NonNull(NonNullPropertyType::String) => { + #[derive(Deserialize)] + struct NonNullStringEnum<'a> { + #[serde(rename = "enum", borrow = "'a")] + _enum: StringEnum<'a, false>, + } + NonNullStringEnum::deserialize(deserializer).map(|c| { + Property::Enum(EnumProperty { + members: EnumMembers::Str { + contains_null: false, + members: c._enum.value, + }, + }) + }) + } + PropertyType::Nullable(NonNullPropertyType::String) => { + #[derive(Deserialize)] + struct NullableStringEnum<'a> { + #[serde(rename = "enum", borrow = "'a")] + _enum: StringEnum<'a, true>, + } + NullableStringEnum::deserialize(deserializer).map(|c| { + Property::Enum(EnumProperty { + members: EnumMembers::Str { + contains_null: true, + members: c._enum.value, + }, + }) + }) + } + ty => Err(de::Error::custom(format!( + "enum of type `{ty}` is not supported" + )))?, + } + } + } + } +} + +impl<'de> Visitor<'de> for PropertyVisitor<'de> { + type Value = (Tag, Content<'de>); + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a property declaration") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut tag_ref: Option = None; + let mut tag_type: Option = None; + let mut tag_enum: Option = None; + let mut vec = Vec::<(Content, Content)>::with_capacity(map.size_hint().unwrap_or_default()); + while let Some(k) = (map.next_key_seed(TagOrContentVisitor::new( + TagKind::ALL.iter().map(|t| (t.as_str(), *t)).collect(), + )))? { + match k { + TagOrContent::Tag(new_tag) => match new_tag { + TagKind::Ref => { + if tag_ref.is_some() { + return Err(de::Error::duplicate_field("$ref")); + } + tag_ref = Some(map.next_value()?); + } + TagKind::Type => { + if tag_type.is_some() { + return Err(de::Error::duplicate_field("type")); + } + tag_type = Some(map.next_value()?); + } + TagKind::Enum => { + if tag_enum.is_some() { + return Err(de::Error::duplicate_field("enum")); + } + tag_enum = Some(map.next_value()?); + } + }, + TagOrContent::Content(k) => { + let v = map.next_value()?; + vec.push((k, v)); + } + } + } + if let Some(ref_value) = tag_ref { + vec.push((Content::String(TagKind::Ref.as_str().into()), ref_value)); + return Ok((Tag::Ref, Content::Map(vec))); + } + if let Some(enum_value) = tag_enum { + if let Some(type_value) = tag_type { + vec.push((Content::String(TagKind::Enum.as_str().into()), enum_value)); + return Ok((Tag::Enum(type_value), Content::Map(vec))); + } else { + return Err(de::Error::missing_field("type")); + } + } + if let Some(type_value) = tag_type { + return Ok((Tag::Type(type_value), Content::Map(vec))); + } + Err(de::Error::custom("missing field `type`, `enum`, or `$ref`")) + } +} + +struct ContentVisitor<'de> { + value: PhantomData>, +} + +impl<'de> ContentVisitor<'de> { + fn new() -> Self { + ContentVisitor { value: PhantomData } + } +} + +impl<'de> Visitor<'de> for ContentVisitor<'de> { + type Value = Content<'de>; + + fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.write_str("any value") + } + + fn visit_bool(self, value: bool) -> Result + where + F: de::Error, + { + Ok(Content::Bool(value)) + } + + fn visit_i64(self, value: i64) -> Result + where + F: de::Error, + { + Ok(Content::I64(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + F: de::Error, + { + Ok(Content::U64(value)) + } + + fn visit_f64(self, value: f64) -> Result + where + F: de::Error, + { + Ok(Content::F64(value)) + } + + fn visit_str(self, value: &str) -> Result + where + F: de::Error, + { + Ok(Content::String(value.into())) + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + F: de::Error, + { + Ok(Content::ByteBuf(value.into())) + } + + fn visit_unit(self) -> Result + where + F: de::Error, + { + Ok(Content::Unit) + } + + fn visit_none(self) -> Result + where + F: de::Error, + { + Ok(Content::None) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(|v| Content::Some(Box::new(v))) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(|v| Content::Newtype(Box::new(v))) + } + + fn visit_seq(self, mut visitor: V) -> Result + where + V: SeqAccess<'de>, + { + let mut vec = Vec::::with_capacity(visitor.size_hint().unwrap_or(0)); + while let Some(e) = visitor.next_element()? { + vec.push(e); + } + Ok(Content::Seq(vec)) + } + + fn visit_map(self, mut visitor: V) -> Result + where + V: MapAccess<'de>, + { + let mut vec = + Vec::<(Content, Content)>::with_capacity(visitor.size_hint().unwrap_or_default()); + while let Some(kv) = visitor.next_entry()? { + vec.push(kv); + } + Ok(Content::Map(vec)) + } + + fn visit_enum(self, _visitor: V) -> Result + where + V: EnumAccess<'de>, + { + Err(de::Error::custom( + "untagged and internally tagged enums do not support enum input", + )) + } +} + +#[derive(Debug)] +enum Tag { + Ref, + Type(PropertyType), + Enum(PropertyType), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum TagKind { + Ref, + Type, + Enum, +} + +impl TagKind { + const REF: &'static str = "$ref"; + const TYPE: &'static str = "type"; + const ENUM: &'static str = "enum"; + const ALL: [Self; 3] = [Self::Ref, Self::Type, Self::Enum]; + fn as_str(&self) -> &'static str { + match self { + TagKind::Ref => Self::REF, + TagKind::Type => Self::TYPE, + TagKind::Enum => Self::ENUM, + } + } +} + +enum TagOrContent<'a> { + Tag(TagKind), + Content(Content<'a>), +} + +struct TagOrContentVisitor<'a> { + names: HashMap<&'a str, TagKind>, +} + +impl<'a> TagOrContentVisitor<'a> { + fn new(names: HashMap<&'a str, TagKind>) -> Self { + Self { names } + } +} + +impl<'de> Visitor<'de> for TagOrContentVisitor<'de> { + type Value = TagOrContent<'de>; + + fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "a type tag or any other value") + } + + fn visit_bool(self, value: bool) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_bool(value) + .map(TagOrContent::Content) + } + + fn visit_i64(self, value: i64) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_i64(value) + .map(TagOrContent::Content) + } + + fn visit_u64(self, value: u64) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_u64(value) + .map(TagOrContent::Content) + } + + fn visit_f64(self, value: f64) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_f64(value) + .map(TagOrContent::Content) + } + + fn visit_str(self, value: &str) -> Result + where + F: de::Error, + { + if let Some(t) = self.names.get(value) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_str(value) + .map(TagOrContent::Content) + } + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + F: de::Error, + { + if let Some((_, t)) = self.names.iter().find(|(k, _)| k.as_bytes() == value) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_bytes(value) + .map(TagOrContent::Content) + } + } + + fn visit_unit(self) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_unit() + .map(TagOrContent::Content) + } + + fn visit_none(self) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_none() + .map(TagOrContent::Content) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + ContentVisitor::new() + .visit_some(deserializer) + .map(TagOrContent::Content) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + ContentVisitor::new() + .visit_newtype_struct(deserializer) + .map(TagOrContent::Content) + } + + fn visit_seq(self, visitor: V) -> Result + where + V: SeqAccess<'de>, + { + ContentVisitor::new() + .visit_seq(visitor) + .map(TagOrContent::Content) + } + + fn visit_map(self, visitor: V) -> Result + where + V: MapAccess<'de>, + { + ContentVisitor::new() + .visit_map(visitor) + .map(TagOrContent::Content) + } + + fn visit_enum(self, visitor: V) -> Result + where + V: EnumAccess<'de>, + { + ContentVisitor::new() + .visit_enum(visitor) + .map(TagOrContent::Content) + } +} + +impl<'de> DeserializeSeed<'de> for TagOrContentVisitor<'de> { + type Value = TagOrContent<'de>; + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // self-describing format + deserializer.deserialize_any(self) + } +} + +#[derive(Debug)] +enum PropertyType { + Null, + NonNull(NonNullPropertyType), + Nullable(NonNullPropertyType), +} + +impl std::fmt::Display for PropertyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PropertyType::Null => write!(f, "null"), + PropertyType::NonNull(ty) => write!(f, "{}", ty.ts_type_str()), + PropertyType::Nullable(ty) => write!(f, "{} | null", ty.ts_type_str()), + } + } +} + +impl From for PropertyType { + fn from(value: PropertyTypeConversion) -> Self { + match value { + PropertyTypeConversion::Null => Self::Null, + PropertyTypeConversion::NonNull(ty) => Self::NonNull(ty), + } + } +} + +#[derive(Debug)] +enum NonNullPropertyType { + Boolean, + Integer, + Object, + Array, + Number, + String, +} + +impl NonNullPropertyType { + fn ts_type_str(&self) -> &'static str { + match self { + NonNullPropertyType::Boolean => "boolean", + NonNullPropertyType::Integer => "number", + NonNullPropertyType::Object => "object", + NonNullPropertyType::Array => "array", + NonNullPropertyType::Number => "number", + NonNullPropertyType::String => "string", + } + } +} + +#[derive(Debug)] +enum PropertyTypeConversion { + Null, + NonNull(NonNullPropertyType), +} + +#[derive(Debug)] +enum PropertyTypeConversionError { + UnsupportedType, +} + +impl NonNullPropertyType { + fn try_from_str(s: &str) -> Result { + Ok(PropertyTypeConversion::NonNull(match s { + "null" => return Ok(PropertyTypeConversion::Null), + "boolean" => Self::Boolean, + "integer" => Self::Integer, + "object" => Self::Object, + "array" => Self::Array, + "number" => Self::Number, + "string" => Self::String, + _ => Err(PropertyTypeConversionError::UnsupportedType)?, + })) + } +} + +struct PropertyTypeVisitor; + +impl<'de> Visitor<'de> for PropertyTypeVisitor { + type Value = PropertyType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a property type") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + let new_ty = NonNullPropertyType::try_from_str(s).map_err(|e| match e { + PropertyTypeConversionError::UnsupportedType => { + de::Error::custom(format!("unsupported property type: `{s}`")) + } + })?; + Ok(match new_ty { + PropertyTypeConversion::Null => PropertyType::Null, + PropertyTypeConversion::NonNull(ty) => PropertyType::NonNull(ty), + }) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut ty = None; + while let Some(s) = seq.next_element::<&str>()? { + let new_ty = NonNullPropertyType::try_from_str(s).map_err(|e| match e { + PropertyTypeConversionError::UnsupportedType => { + de::Error::custom(format!("unsupported property type: `{s}`")) + } + })?; + if let Some(old_ty) = ty { + match (old_ty, new_ty) { + (PropertyType::Null, PropertyTypeConversion::NonNull(nn)) + | (PropertyType::NonNull(nn), PropertyTypeConversion::Null) => { + ty = Some(PropertyType::Nullable(nn)); + } + (old_ty, new_ty) => { + return Err(de::Error::custom( + format!("unimplemented combination of property types (bug): `{old_ty:?}` and `{new_ty:?}`"), + )); + } + }; + } else { + ty = Some(new_ty.into()); + } + } + match ty { + None => Err(de::Error::custom("missing property `type`")), + Some(ty) => Ok(ty), + } + } +} + +impl<'de> Deserialize<'de> for PropertyType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(PropertyTypeVisitor) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_schema_version() { + let s = r#" + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + "#; + let _schema: SchemaVersion = serde_json::from_str(s).unwrap(); + } + #[test] + fn test_schema() { + let s = r##" + { + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "branch_protection_rule$created": { + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "description": "Activity related to a branch protection rule. For more information, see \"[About branch protection rules](https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches#about-branch-protection-rules).\"", + "required": ["action", "rule", "repository", "sender"], + "properties": { + "action": { "type": "string", "enum": ["created"] }, + "rule": { "$ref": "#/definitions/branch-protection-rule" }, + "repository": { "$ref": "#/definitions/repository" }, + "sender": { "$ref": "#/definitions/user" }, + "installation": { "$ref": "#/definitions/installation-lite" }, + "organization": { "$ref": "#/definitions/organization" } + }, + "title": "branch protection rule created event", + "additionalProperties": false + } + }, + "oneOf": [ + { "$ref": "#/definitions/branch_protection_rule_event" } + ] + }"##; + let _schema: JsonSchemaRoot = serde_json::from_str(s).unwrap(); + dbg!(_schema); + } +} From 0dd869b56227376d5692b16eba5f9f053e5c1d53 Mon Sep 17 00:00:00 2001 From: ylides Date: Wed, 30 Aug 2023 06:20:16 +0900 Subject: [PATCH 3/9] reorder definitions and uses --- type-generator/src/frontend/syntax.rs | 939 +++++++++++++------------- 1 file changed, 488 insertions(+), 451 deletions(-) diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs index 6e49f27..86ad93e 100644 --- a/type-generator/src/frontend/syntax.rs +++ b/type-generator/src/frontend/syntax.rs @@ -1,9 +1,10 @@ use std::{collections::HashMap, marker::PhantomData}; -use serde::__private::de::Content; -use serde::de::{self, EnumAccess, MapAccess, SeqAccess}; +// use this module because deserializing `Property<'a>` resembles that of internally tagged enums, which "use"s it +// we can't simply use internal tagged enums because the property "type" is not always a string +use serde::__private::de::{Content, ContentDeserializer}; use serde::{ - de::{DeserializeSeed, Visitor}, + de::{self, SeqAccess, Visitor}, Deserialize, Deserializer, }; @@ -24,7 +25,7 @@ struct SchemaVersion { } mod schema_version { - use serde::{self, Deserialize, Deserializer}; + use serde::{Deserialize, Deserializer}; pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> where @@ -117,23 +118,176 @@ pub enum EnumMembers<'a> { Boolean(Vec), } -struct PropertyVisitor<'a> { - value: PhantomData>, -} - -impl<'a> PropertyVisitor<'a> { - fn new() -> Self { - Self { value: PhantomData } - } -} - impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { + use tagged_enum::*; + + struct PropertyVisitor<'a> { + value: PhantomData>, + } + + impl<'a> PropertyVisitor<'a> { + fn new() -> Self { + Self { value: PhantomData } + } + } + + impl<'de> Visitor<'de> for PropertyVisitor<'de> { + type Value = (Tag, Content<'de>); + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a property declaration") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + impl<'de> Deserialize<'de> for PropertyType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct PropertyTypeVisitor; + + impl<'de> Visitor<'de> for PropertyTypeVisitor { + type Value = PropertyType; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("a property type") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + let new_ty = + NonNullPropertyType::try_from_str(s).map_err(|e| match e { + PropertyTypeConversionError::UnsupportedType => { + de::Error::custom(format!( + "unsupported property type: `{s}`" + )) + } + })?; + Ok(match new_ty { + PropertyTypeConversion::Null => PropertyType::Null, + PropertyTypeConversion::NonNull(ty) => { + PropertyType::NonNull(ty) + } + }) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut ty = None; + while let Some(s) = seq.next_element::<&str>()? { + let new_ty = NonNullPropertyType::try_from_str(s).map_err( + |e| match e { + PropertyTypeConversionError::UnsupportedType => { + de::Error::custom(format!( + "unsupported property type: `{s}`" + )) + } + }, + )?; + if let Some(old_ty) = ty { + match (old_ty, new_ty) { + ( + PropertyType::Null, + PropertyTypeConversion::NonNull(nn), + ) + | ( + PropertyType::NonNull(nn), + PropertyTypeConversion::Null, + ) => { + ty = Some(PropertyType::Nullable(nn)); + } + (old_ty, new_ty) => { + return Err(de::Error::custom(format!("unimplemented combination of property types (bug): `{old_ty:?}` and `{new_ty:?}`"))); + } + }; + } else { + ty = Some(new_ty.into()); + } + } + match ty { + None => Err(de::Error::custom("missing property `type`")), + Some(ty) => Ok(ty), + } + } + } + + deserializer.deserialize_any(PropertyTypeVisitor) + } + } + + let mut tag_ref: Option = None; + let mut tag_type: Option = None; + let mut tag_enum: Option = None; + let mut vec = + Vec::<(Content, Content)>::with_capacity(map.size_hint().unwrap_or_default()); + // use of `<'de> TagOrContentVisitor<'de>: DeserializeSeed<'de>` + while let Some(k) = (map.next_key_seed(TagOrContentVisitor::new( + TagKind::ALL.iter().map(|t| (t.as_str(), *t)).collect(), + )))? { + match k { + TagOrContent::Tag(new_tag) => match new_tag { + TagKind::Ref => { + if tag_ref.is_some() { + return Err(de::Error::duplicate_field("$ref")); + } + tag_ref = Some(map.next_value::()?); + } + TagKind::Type => { + if tag_type.is_some() { + return Err(de::Error::duplicate_field("type")); + } + // use of `<'de: 'a, 'a> PropertyType<'a>: Deserialize<'de>` + tag_type = Some(map.next_value::()?); + } + TagKind::Enum => { + if tag_enum.is_some() { + return Err(de::Error::duplicate_field("enum")); + } + tag_enum = Some(map.next_value::()?); + } + }, + TagOrContent::Content(k) => { + let v = map.next_value()?; + vec.push((k, v)); + } + } + } + if let Some(ref_value) = tag_ref { + vec.push((Content::String(TagKind::Ref.as_str().into()), ref_value)); + return Ok((Tag::Ref, Content::Map(vec))); + } + if let Some(enum_value) = tag_enum { + if let Some(type_value) = tag_type { + vec.push((Content::String(TagKind::Enum.as_str().into()), enum_value)); + return Ok((Tag::Enum(type_value), Content::Map(vec))); + } else { + return Err(de::Error::missing_field("type")); + } + } + if let Some(type_value) = tag_type { + return Ok((Tag::Type(type_value), Content::Map(vec))); + } + Err(de::Error::custom("missing field `type`, `enum`, or `$ref`")) + } + } + + // use of `<'de> PropertyVisitor<'de>: Visitor<'de>` let (tag, content) = deserializer.deserialize_any(PropertyVisitor::new())?; - let deserializer = serde::__private::de::ContentDeserializer::::new(content); + let deserializer = ContentDeserializer::::new(content); match tag { Tag::Ref => RefProperty::deserialize(deserializer).map(Property::Ref), Tag::Type(ty) => { @@ -289,517 +443,400 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { } } -impl<'de> Visitor<'de> for PropertyVisitor<'de> { - type Value = (Tag, Content<'de>); +mod tagged_enum { + use std::{collections::HashMap, marker::PhantomData}; + + // use this module because deserializing `Property<'a>` resembles that of internally tagged enums, which "use"s it + // we can't simply use internal tagged enums because the property "type" is not always a string + use serde::__private::de::Content; + use serde::{ + de::{self, DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Visitor}, + Deserialize, Deserializer, + }; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a property declaration") + pub struct ContentVisitor<'de> { + value: PhantomData>, } - fn visit_map(self, mut map: A) -> Result - where - A: de::MapAccess<'de>, - { - let mut tag_ref: Option = None; - let mut tag_type: Option = None; - let mut tag_enum: Option = None; - let mut vec = Vec::<(Content, Content)>::with_capacity(map.size_hint().unwrap_or_default()); - while let Some(k) = (map.next_key_seed(TagOrContentVisitor::new( - TagKind::ALL.iter().map(|t| (t.as_str(), *t)).collect(), - )))? { - match k { - TagOrContent::Tag(new_tag) => match new_tag { - TagKind::Ref => { - if tag_ref.is_some() { - return Err(de::Error::duplicate_field("$ref")); - } - tag_ref = Some(map.next_value()?); - } - TagKind::Type => { - if tag_type.is_some() { - return Err(de::Error::duplicate_field("type")); - } - tag_type = Some(map.next_value()?); - } - TagKind::Enum => { - if tag_enum.is_some() { - return Err(de::Error::duplicate_field("enum")); - } - tag_enum = Some(map.next_value()?); - } - }, - TagOrContent::Content(k) => { - let v = map.next_value()?; - vec.push((k, v)); - } - } - } - if let Some(ref_value) = tag_ref { - vec.push((Content::String(TagKind::Ref.as_str().into()), ref_value)); - return Ok((Tag::Ref, Content::Map(vec))); - } - if let Some(enum_value) = tag_enum { - if let Some(type_value) = tag_type { - vec.push((Content::String(TagKind::Enum.as_str().into()), enum_value)); - return Ok((Tag::Enum(type_value), Content::Map(vec))); - } else { - return Err(de::Error::missing_field("type")); - } - } - if let Some(type_value) = tag_type { - return Ok((Tag::Type(type_value), Content::Map(vec))); + impl<'de> ContentVisitor<'de> { + fn new() -> Self { + ContentVisitor { value: PhantomData } } - Err(de::Error::custom("missing field `type`, `enum`, or `$ref`")) } -} - -struct ContentVisitor<'de> { - value: PhantomData>, -} -impl<'de> ContentVisitor<'de> { - fn new() -> Self { - ContentVisitor { value: PhantomData } - } -} + impl<'de> Visitor<'de> for ContentVisitor<'de> { + type Value = Content<'de>; -impl<'de> Visitor<'de> for ContentVisitor<'de> { - type Value = Content<'de>; + fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.write_str("any value") + } - fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - fmt.write_str("any value") - } + fn visit_bool(self, value: bool) -> Result + where + F: de::Error, + { + Ok(Content::Bool(value)) + } - fn visit_bool(self, value: bool) -> Result - where - F: de::Error, - { - Ok(Content::Bool(value)) - } + fn visit_i64(self, value: i64) -> Result + where + F: de::Error, + { + Ok(Content::I64(value)) + } - fn visit_i64(self, value: i64) -> Result - where - F: de::Error, - { - Ok(Content::I64(value)) - } + fn visit_u64(self, value: u64) -> Result + where + F: de::Error, + { + Ok(Content::U64(value)) + } - fn visit_u64(self, value: u64) -> Result - where - F: de::Error, - { - Ok(Content::U64(value)) - } + fn visit_f64(self, value: f64) -> Result + where + F: de::Error, + { + Ok(Content::F64(value)) + } - fn visit_f64(self, value: f64) -> Result - where - F: de::Error, - { - Ok(Content::F64(value)) - } + fn visit_str(self, value: &str) -> Result + where + F: de::Error, + { + Ok(Content::String(value.into())) + } - fn visit_str(self, value: &str) -> Result - where - F: de::Error, - { - Ok(Content::String(value.into())) - } + fn visit_bytes(self, value: &[u8]) -> Result + where + F: de::Error, + { + Ok(Content::ByteBuf(value.into())) + } - fn visit_bytes(self, value: &[u8]) -> Result - where - F: de::Error, - { - Ok(Content::ByteBuf(value.into())) - } + fn visit_unit(self) -> Result + where + F: de::Error, + { + Ok(Content::Unit) + } - fn visit_unit(self) -> Result - where - F: de::Error, - { - Ok(Content::Unit) - } + fn visit_none(self) -> Result + where + F: de::Error, + { + Ok(Content::None) + } - fn visit_none(self) -> Result - where - F: de::Error, - { - Ok(Content::None) - } + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(|v| Content::Some(Box::new(v))) + } - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Deserialize::deserialize(deserializer).map(|v| Content::Some(Box::new(v))) - } + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(|v| Content::Newtype(Box::new(v))) + } - fn visit_newtype_struct(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Deserialize::deserialize(deserializer).map(|v| Content::Newtype(Box::new(v))) - } + fn visit_seq(self, mut visitor: V) -> Result + where + V: SeqAccess<'de>, + { + let mut vec = Vec::::with_capacity(visitor.size_hint().unwrap_or(0)); + while let Some(e) = visitor.next_element()? { + vec.push(e); + } + Ok(Content::Seq(vec)) + } - fn visit_seq(self, mut visitor: V) -> Result - where - V: SeqAccess<'de>, - { - let mut vec = Vec::::with_capacity(visitor.size_hint().unwrap_or(0)); - while let Some(e) = visitor.next_element()? { - vec.push(e); + fn visit_map(self, mut visitor: V) -> Result + where + V: MapAccess<'de>, + { + let mut vec = + Vec::<(Content, Content)>::with_capacity(visitor.size_hint().unwrap_or_default()); + while let Some(kv) = visitor.next_entry()? { + vec.push(kv); + } + Ok(Content::Map(vec)) } - Ok(Content::Seq(vec)) - } - fn visit_map(self, mut visitor: V) -> Result - where - V: MapAccess<'de>, - { - let mut vec = - Vec::<(Content, Content)>::with_capacity(visitor.size_hint().unwrap_or_default()); - while let Some(kv) = visitor.next_entry()? { - vec.push(kv); + fn visit_enum(self, _visitor: V) -> Result + where + V: EnumAccess<'de>, + { + Err(de::Error::custom( + "untagged and internally tagged enums do not support enum input", + )) } - Ok(Content::Map(vec)) } - fn visit_enum(self, _visitor: V) -> Result - where - V: EnumAccess<'de>, - { - Err(de::Error::custom( - "untagged and internally tagged enums do not support enum input", - )) + #[derive(Debug)] + pub enum Tag { + Ref, + Type(PropertyType), + Enum(PropertyType), } -} -#[derive(Debug)] -enum Tag { - Ref, - Type(PropertyType), - Enum(PropertyType), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum TagKind { - Ref, - Type, - Enum, -} - -impl TagKind { - const REF: &'static str = "$ref"; - const TYPE: &'static str = "type"; - const ENUM: &'static str = "enum"; - const ALL: [Self; 3] = [Self::Ref, Self::Type, Self::Enum]; - fn as_str(&self) -> &'static str { - match self { - TagKind::Ref => Self::REF, - TagKind::Type => Self::TYPE, - TagKind::Enum => Self::ENUM, - } + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum TagKind { + Ref, + Type, + Enum, } -} - -enum TagOrContent<'a> { - Tag(TagKind), - Content(Content<'a>), -} -struct TagOrContentVisitor<'a> { - names: HashMap<&'a str, TagKind>, -} - -impl<'a> TagOrContentVisitor<'a> { - fn new(names: HashMap<&'a str, TagKind>) -> Self { - Self { names } + impl TagKind { + pub const REF: &'static str = "$ref"; + pub const TYPE: &'static str = "type"; + pub const ENUM: &'static str = "enum"; + pub const ALL: [Self; 3] = [Self::Ref, Self::Type, Self::Enum]; + pub fn as_str(&self) -> &'static str { + match self { + TagKind::Ref => Self::REF, + TagKind::Type => Self::TYPE, + TagKind::Enum => Self::ENUM, + } + } } -} -impl<'de> Visitor<'de> for TagOrContentVisitor<'de> { - type Value = TagOrContent<'de>; - - fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(fmt, "a type tag or any other value") + pub enum TagOrContent<'a> { + Tag(TagKind), + Content(Content<'a>), } - fn visit_bool(self, value: bool) -> Result - where - F: de::Error, - { - ContentVisitor::new() - .visit_bool(value) - .map(TagOrContent::Content) + pub struct TagOrContentVisitor<'a> { + names: HashMap<&'a str, TagKind>, } - fn visit_i64(self, value: i64) -> Result - where - F: de::Error, - { - ContentVisitor::new() - .visit_i64(value) - .map(TagOrContent::Content) + impl<'a> TagOrContentVisitor<'a> { + pub fn new(names: HashMap<&'a str, TagKind>) -> Self { + Self { names } + } } - fn visit_u64(self, value: u64) -> Result - where - F: de::Error, - { - ContentVisitor::new() - .visit_u64(value) - .map(TagOrContent::Content) - } + impl<'de> Visitor<'de> for TagOrContentVisitor<'de> { + type Value = TagOrContent<'de>; - fn visit_f64(self, value: f64) -> Result - where - F: de::Error, - { - ContentVisitor::new() - .visit_f64(value) - .map(TagOrContent::Content) - } + fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "a type tag or any other value") + } - fn visit_str(self, value: &str) -> Result - where - F: de::Error, - { - if let Some(t) = self.names.get(value) { - Ok(TagOrContent::Tag(*t)) - } else { + fn visit_bool(self, value: bool) -> Result + where + F: de::Error, + { ContentVisitor::new() - .visit_str(value) + .visit_bool(value) .map(TagOrContent::Content) } - } - fn visit_bytes(self, value: &[u8]) -> Result - where - F: de::Error, - { - if let Some((_, t)) = self.names.iter().find(|(k, _)| k.as_bytes() == value) { - Ok(TagOrContent::Tag(*t)) - } else { + fn visit_i64(self, value: i64) -> Result + where + F: de::Error, + { ContentVisitor::new() - .visit_bytes(value) + .visit_i64(value) .map(TagOrContent::Content) } - } - fn visit_unit(self) -> Result - where - F: de::Error, - { - ContentVisitor::new() - .visit_unit() - .map(TagOrContent::Content) - } - - fn visit_none(self) -> Result - where - F: de::Error, - { - ContentVisitor::new() - .visit_none() - .map(TagOrContent::Content) - } + fn visit_u64(self, value: u64) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_u64(value) + .map(TagOrContent::Content) + } - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - ContentVisitor::new() - .visit_some(deserializer) - .map(TagOrContent::Content) - } + fn visit_f64(self, value: f64) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_f64(value) + .map(TagOrContent::Content) + } - fn visit_newtype_struct(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - ContentVisitor::new() - .visit_newtype_struct(deserializer) - .map(TagOrContent::Content) - } + fn visit_str(self, value: &str) -> Result + where + F: de::Error, + { + if let Some(t) = self.names.get(value) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_str(value) + .map(TagOrContent::Content) + } + } - fn visit_seq(self, visitor: V) -> Result - where - V: SeqAccess<'de>, - { - ContentVisitor::new() - .visit_seq(visitor) - .map(TagOrContent::Content) - } + fn visit_bytes(self, value: &[u8]) -> Result + where + F: de::Error, + { + if let Some((_, t)) = self.names.iter().find(|(k, _)| k.as_bytes() == value) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_bytes(value) + .map(TagOrContent::Content) + } + } - fn visit_map(self, visitor: V) -> Result - where - V: MapAccess<'de>, - { - ContentVisitor::new() - .visit_map(visitor) - .map(TagOrContent::Content) - } + fn visit_unit(self) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_unit() + .map(TagOrContent::Content) + } - fn visit_enum(self, visitor: V) -> Result - where - V: EnumAccess<'de>, - { - ContentVisitor::new() - .visit_enum(visitor) - .map(TagOrContent::Content) - } -} + fn visit_none(self) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_none() + .map(TagOrContent::Content) + } -impl<'de> DeserializeSeed<'de> for TagOrContentVisitor<'de> { - type Value = TagOrContent<'de>; + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + ContentVisitor::new() + .visit_some(deserializer) + .map(TagOrContent::Content) + } - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - // self-describing format - deserializer.deserialize_any(self) - } -} + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + ContentVisitor::new() + .visit_newtype_struct(deserializer) + .map(TagOrContent::Content) + } -#[derive(Debug)] -enum PropertyType { - Null, - NonNull(NonNullPropertyType), - Nullable(NonNullPropertyType), -} + fn visit_seq(self, visitor: V) -> Result + where + V: SeqAccess<'de>, + { + ContentVisitor::new() + .visit_seq(visitor) + .map(TagOrContent::Content) + } -impl std::fmt::Display for PropertyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - PropertyType::Null => write!(f, "null"), - PropertyType::NonNull(ty) => write!(f, "{}", ty.ts_type_str()), - PropertyType::Nullable(ty) => write!(f, "{} | null", ty.ts_type_str()), + fn visit_map(self, visitor: V) -> Result + where + V: MapAccess<'de>, + { + ContentVisitor::new() + .visit_map(visitor) + .map(TagOrContent::Content) } - } -} -impl From for PropertyType { - fn from(value: PropertyTypeConversion) -> Self { - match value { - PropertyTypeConversion::Null => Self::Null, - PropertyTypeConversion::NonNull(ty) => Self::NonNull(ty), + fn visit_enum(self, visitor: V) -> Result + where + V: EnumAccess<'de>, + { + ContentVisitor::new() + .visit_enum(visitor) + .map(TagOrContent::Content) } } -} -#[derive(Debug)] -enum NonNullPropertyType { - Boolean, - Integer, - Object, - Array, - Number, - String, -} + impl<'de> DeserializeSeed<'de> for TagOrContentVisitor<'de> { + type Value = TagOrContent<'de>; -impl NonNullPropertyType { - fn ts_type_str(&self) -> &'static str { - match self { - NonNullPropertyType::Boolean => "boolean", - NonNullPropertyType::Integer => "number", - NonNullPropertyType::Object => "object", - NonNullPropertyType::Array => "array", - NonNullPropertyType::Number => "number", - NonNullPropertyType::String => "string", + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // self-describing format + deserializer.deserialize_any(self) } } -} - -#[derive(Debug)] -enum PropertyTypeConversion { - Null, - NonNull(NonNullPropertyType), -} - -#[derive(Debug)] -enum PropertyTypeConversionError { - UnsupportedType, -} -impl NonNullPropertyType { - fn try_from_str(s: &str) -> Result { - Ok(PropertyTypeConversion::NonNull(match s { - "null" => return Ok(PropertyTypeConversion::Null), - "boolean" => Self::Boolean, - "integer" => Self::Integer, - "object" => Self::Object, - "array" => Self::Array, - "number" => Self::Number, - "string" => Self::String, - _ => Err(PropertyTypeConversionError::UnsupportedType)?, - })) + #[derive(Debug)] + pub enum PropertyType { + Null, + NonNull(NonNullPropertyType), + Nullable(NonNullPropertyType), } -} - -struct PropertyTypeVisitor; -impl<'de> Visitor<'de> for PropertyTypeVisitor { - type Value = PropertyType; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a property type") + impl std::fmt::Display for PropertyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PropertyType::Null => write!(f, "null"), + PropertyType::NonNull(ty) => write!(f, "{}", ty.ts_type_str()), + PropertyType::Nullable(ty) => write!(f, "{} | null", ty.ts_type_str()), + } + } } - fn visit_str(self, s: &str) -> Result - where - E: de::Error, - { - let new_ty = NonNullPropertyType::try_from_str(s).map_err(|e| match e { - PropertyTypeConversionError::UnsupportedType => { - de::Error::custom(format!("unsupported property type: `{s}`")) + impl From for PropertyType { + fn from(value: PropertyTypeConversion) -> Self { + match value { + PropertyTypeConversion::Null => Self::Null, + PropertyTypeConversion::NonNull(ty) => Self::NonNull(ty), } - })?; - Ok(match new_ty { - PropertyTypeConversion::Null => PropertyType::Null, - PropertyTypeConversion::NonNull(ty) => PropertyType::NonNull(ty), - }) + } } - fn visit_seq(self, mut seq: A) -> Result - where - A: de::SeqAccess<'de>, - { - let mut ty = None; - while let Some(s) = seq.next_element::<&str>()? { - let new_ty = NonNullPropertyType::try_from_str(s).map_err(|e| match e { - PropertyTypeConversionError::UnsupportedType => { - de::Error::custom(format!("unsupported property type: `{s}`")) - } - })?; - if let Some(old_ty) = ty { - match (old_ty, new_ty) { - (PropertyType::Null, PropertyTypeConversion::NonNull(nn)) - | (PropertyType::NonNull(nn), PropertyTypeConversion::Null) => { - ty = Some(PropertyType::Nullable(nn)); - } - (old_ty, new_ty) => { - return Err(de::Error::custom( - format!("unimplemented combination of property types (bug): `{old_ty:?}` and `{new_ty:?}`"), - )); - } - }; - } else { - ty = Some(new_ty.into()); + #[derive(Debug)] + pub enum NonNullPropertyType { + Boolean, + Integer, + Object, + Array, + Number, + String, + } + + impl NonNullPropertyType { + fn ts_type_str(&self) -> &'static str { + match self { + NonNullPropertyType::Boolean => "boolean", + NonNullPropertyType::Integer => "number", + NonNullPropertyType::Object => "object", + NonNullPropertyType::Array => "array", + NonNullPropertyType::Number => "number", + NonNullPropertyType::String => "string", } } - match ty { - None => Err(de::Error::custom("missing property `type`")), - Some(ty) => Ok(ty), - } } -} -impl<'de> Deserialize<'de> for PropertyType { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_any(PropertyTypeVisitor) + #[derive(Debug)] + pub enum PropertyTypeConversion { + Null, + NonNull(NonNullPropertyType), + } + + #[derive(Debug)] + pub enum PropertyTypeConversionError { + UnsupportedType, + } + + impl NonNullPropertyType { + pub fn try_from_str( + s: &str, + ) -> Result { + Ok(PropertyTypeConversion::NonNull(match s { + "null" => return Ok(PropertyTypeConversion::Null), + "boolean" => Self::Boolean, + "integer" => Self::Integer, + "object" => Self::Object, + "array" => Self::Array, + "number" => Self::Number, + "string" => Self::String, + _ => Err(PropertyTypeConversionError::UnsupportedType)?, + })) + } } } From f0f5f3e18bb5b84f5230fe9acb881a9321edfe5d Mon Sep 17 00:00:00 2001 From: ylides Date: Thu, 31 Aug 2023 05:03:33 +0900 Subject: [PATCH 4/9] support any type --- type-generator/src/frontend/syntax.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs index 86ad93e..4ea9267 100644 --- a/type-generator/src/frontend/syntax.rs +++ b/type-generator/src/frontend/syntax.rs @@ -49,6 +49,7 @@ pub enum Property<'a> { Ref(RefProperty<'a>), Value(TypedValue<'a>), Enum(EnumProperty<'a>), + Any, } #[derive(Debug, Deserialize)] @@ -281,7 +282,10 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { if let Some(type_value) = tag_type { return Ok((Tag::Type(type_value), Content::Map(vec))); } - Err(de::Error::custom("missing field `type`, `enum`, or `$ref`")) + if vec.is_empty() { + return Ok((Tag::None, Content::None)); + } + Err(de::Error::custom("unsupported property type")) } } @@ -439,6 +443,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { )))?, } } + Tag::None => Ok(Property::Any), } } } @@ -579,6 +584,7 @@ mod tagged_enum { Ref, Type(PropertyType), Enum(PropertyType), + None, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] From fbc4253f7c0c9865029aa8f0cbd57d92b9bc30ed Mon Sep 17 00:00:00 2001 From: ylides Date: Thu, 31 Aug 2023 05:17:24 +0900 Subject: [PATCH 5/9] preserve defined order of properties --- type-generator/src/frontend/syntax.rs | 47 +++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs index 4ea9267..4d93602 100644 --- a/type-generator/src/frontend/syntax.rs +++ b/type-generator/src/frontend/syntax.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, marker::PhantomData}; +use std::marker::PhantomData; // use this module because deserializing `Property<'a>` resembles that of internally tagged enums, which "use"s it // we can't simply use internal tagged enums because the property "type" is not always a string @@ -14,7 +14,7 @@ pub struct JsonSchemaRoot<'a> { #[serde(flatten)] _schema: SchemaVersion, #[serde(borrow)] - pub definitions: HashMap<&'a str, Property<'a>>, + pub definitions: ObjectProperties<'a>, pub one_of: Vec>, } @@ -74,12 +74,53 @@ pub enum StringFormat { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ObjectProperty<'a> { - pub properties: HashMap<&'a str, Property<'a>>, + pub properties: ObjectProperties<'a>, pub required: Option>, pub title: Option<&'a str>, pub additional_properties: bool, } +#[derive(Debug)] +pub struct ObjectProperties<'a>(pub Vec<(&'a str, Property<'a>)>); + +impl<'de: 'a, 'a> Deserialize<'de> for ObjectProperties<'a> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ObjectPropertiesVisitor<'a> { + value: PhantomData>, + } + + impl<'a> ObjectPropertiesVisitor<'a> { + fn new() -> Self { + Self { value: PhantomData } + } + } + + impl<'de: 'a, 'a> Visitor<'de> for ObjectPropertiesVisitor<'a> { + type Value = ObjectProperties<'a>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an object property declaration") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut vec = Vec::with_capacity(map.size_hint().unwrap_or_default()); + while let Some(t) = map.next_entry()? { + vec.push(t); + } + Ok(ObjectProperties(vec)) + } + } + + deserializer.deserialize_map(ObjectPropertiesVisitor::new()) + } +} + #[derive(Debug, Deserialize)] pub struct ArrayProperty<'a> { #[serde(borrow = "'a")] From c5c8168e45329f6c7582e66f4236a548153d7f8c Mon Sep 17 00:00:00 2001 From: ylides Date: Thu, 31 Aug 2023 06:09:34 +0900 Subject: [PATCH 6/9] insert description prop --- type-generator/src/frontend/syntax.rs | 60 ++++++++++++++++++++------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs index 4d93602..cc86752 100644 --- a/type-generator/src/frontend/syntax.rs +++ b/type-generator/src/frontend/syntax.rs @@ -44,8 +44,17 @@ mod schema_version { } } +#[derive(Debug, Deserialize)] +pub struct Property<'a> { + #[serde(default)] + /// `Option<&'a str>` doesn't work (?) + pub description: Option, + #[serde(flatten, borrow)] + pub kind: PropertyKind<'a>, +} + #[derive(Debug)] -pub enum Property<'a> { +pub enum PropertyKind<'a> { Ref(RefProperty<'a>), Value(TypedValue<'a>), Enum(EnumProperty<'a>), @@ -124,7 +133,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for ObjectProperties<'a> { #[derive(Debug, Deserialize)] pub struct ArrayProperty<'a> { #[serde(borrow = "'a")] - pub items: Box>, + pub items: Box>, } #[derive(Debug)] @@ -160,7 +169,7 @@ pub enum EnumMembers<'a> { Boolean(Vec), } -impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { +impl<'de: 'a, 'a> Deserialize<'de> for PropertyKind<'a> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -168,7 +177,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { use tagged_enum::*; struct PropertyVisitor<'a> { - value: PhantomData>, + value: PhantomData>, } impl<'a> PropertyVisitor<'a> { @@ -334,29 +343,29 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { let (tag, content) = deserializer.deserialize_any(PropertyVisitor::new())?; let deserializer = ContentDeserializer::::new(content); match tag { - Tag::Ref => RefProperty::deserialize(deserializer).map(Property::Ref), + Tag::Ref => RefProperty::deserialize(deserializer).map(PropertyKind::Ref), Tag::Type(ty) => { let (ty, is_nullable) = match ty { - PropertyType::Null => return Ok(Property::Value(TypedValue::Null)), + PropertyType::Null => return Ok(PropertyKind::Value(TypedValue::Null)), PropertyType::NonNull(ty) => (ty, false), PropertyType::Nullable(ty) => (ty, true), }; match ty { - NonNullPropertyType::Boolean => Ok(Property::Value(TypedValue::Other { + NonNullPropertyType::Boolean => Ok(PropertyKind::Value(TypedValue::Other { is_nullable, value: NonNullValue::Boolean, })), - NonNullPropertyType::Integer => Ok(Property::Value(TypedValue::Other { + NonNullPropertyType::Integer => Ok(PropertyKind::Value(TypedValue::Other { is_nullable, value: NonNullValue::Integer, })), - NonNullPropertyType::Number => Ok(Property::Value(TypedValue::Other { + NonNullPropertyType::Number => Ok(PropertyKind::Value(TypedValue::Other { is_nullable, value: NonNullValue::Number, })), NonNullPropertyType::String => { StringProperty::deserialize(deserializer).map(|c| { - Property::Value(TypedValue::Other { + PropertyKind::Value(TypedValue::Other { is_nullable, value: NonNullValue::String(c), }) @@ -364,7 +373,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { } NonNullPropertyType::Object => { ObjectProperty::deserialize(deserializer).map(|c| { - Property::Value(TypedValue::Other { + PropertyKind::Value(TypedValue::Other { is_nullable, value: NonNullValue::Object(c), }) @@ -372,7 +381,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { } NonNullPropertyType::Array => { ArrayProperty::deserialize(deserializer).map(|c| { - Property::Value(TypedValue::Other { + PropertyKind::Value(TypedValue::Other { is_nullable, value: NonNullValue::Array(c), }) @@ -444,7 +453,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { _enum: Vec, } BooleanEnum::deserialize(deserializer).map(|c| { - Property::Enum(EnumProperty { + PropertyKind::Enum(EnumProperty { members: EnumMembers::Boolean(c._enum), }) }) @@ -456,7 +465,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { _enum: StringEnum<'a, false>, } NonNullStringEnum::deserialize(deserializer).map(|c| { - Property::Enum(EnumProperty { + PropertyKind::Enum(EnumProperty { members: EnumMembers::Str { contains_null: false, members: c._enum.value, @@ -471,7 +480,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { _enum: StringEnum<'a, true>, } NullableStringEnum::deserialize(deserializer).map(|c| { - Property::Enum(EnumProperty { + PropertyKind::Enum(EnumProperty { members: EnumMembers::Str { contains_null: true, members: c._enum.value, @@ -484,7 +493,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for Property<'a> { )))?, } } - Tag::None => Ok(Property::Any), + Tag::None => Ok(PropertyKind::Any), } } } @@ -927,6 +936,25 @@ mod tests { }, "title": "branch protection rule created event", "additionalProperties": false + }, + "issues$labeled": { + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "required": ["action", "issue", "repository", "sender"], + "properties": { + "action": { "type": "string", "enum": ["labeled"] }, + "issue": { "$ref": "#/definitions/issue" }, + "label": { + "$ref": "#/definitions/label", + "description": "The label that was added to the issue." + }, + "repository": { "$ref": "#/definitions/repository" }, + "sender": { "$ref": "#/definitions/user" }, + "installation": { "$ref": "#/definitions/installation-lite" }, + "organization": { "$ref": "#/definitions/organization" } + }, + "additionalProperties": false, + "title": "issues labeled event" } }, "oneOf": [ From f10f3fd42cb56b7ac3305d0d2b9b695f6db28210 Mon Sep 17 00:00:00 2001 From: ylides Date: Thu, 31 Aug 2023 07:27:05 +0900 Subject: [PATCH 7/9] support oneOf, allOf --- type-generator/src/frontend/syntax.rs | 287 +++++++++++++++++++++++++- 1 file changed, 277 insertions(+), 10 deletions(-) diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs index cc86752..6ad323d 100644 --- a/type-generator/src/frontend/syntax.rs +++ b/type-generator/src/frontend/syntax.rs @@ -46,8 +46,6 @@ mod schema_version { #[derive(Debug, Deserialize)] pub struct Property<'a> { - #[serde(default)] - /// `Option<&'a str>` doesn't work (?) pub description: Option, #[serde(flatten, borrow)] pub kind: PropertyKind<'a>, @@ -56,6 +54,8 @@ pub struct Property<'a> { #[derive(Debug)] pub enum PropertyKind<'a> { Ref(RefProperty<'a>), + OneOf(Vec), + AllOf(Vec), Value(TypedValue<'a>), Enum(EnumProperty<'a>), Any, @@ -86,7 +86,7 @@ pub struct ObjectProperty<'a> { pub properties: ObjectProperties<'a>, pub required: Option>, pub title: Option<&'a str>, - pub additional_properties: bool, + pub additional_properties: Option, } #[derive(Debug)] @@ -281,6 +281,8 @@ impl<'de: 'a, 'a> Deserialize<'de> for PropertyKind<'a> { } let mut tag_ref: Option = None; + let mut tag_oneof: Option = None; + let mut tag_allof: Option = None; let mut tag_type: Option = None; let mut tag_enum: Option = None; let mut vec = @@ -290,23 +292,35 @@ impl<'de: 'a, 'a> Deserialize<'de> for PropertyKind<'a> { TagKind::ALL.iter().map(|t| (t.as_str(), *t)).collect(), )))? { match k { - TagOrContent::Tag(new_tag) => match new_tag { + TagOrContent::Tag(new_tag) => match &new_tag { TagKind::Ref => { if tag_ref.is_some() { - return Err(de::Error::duplicate_field("$ref")); + return Err(de::Error::duplicate_field(new_tag.as_str())); } tag_ref = Some(map.next_value::()?); } + TagKind::OneOf => { + if tag_oneof.is_some() { + return Err(de::Error::duplicate_field(new_tag.as_str())); + } + tag_oneof = Some(map.next_value::()?); + } + TagKind::AllOf => { + if tag_allof.is_some() { + return Err(de::Error::duplicate_field(new_tag.as_str())); + } + tag_allof = Some(map.next_value::()?); + } TagKind::Type => { if tag_type.is_some() { - return Err(de::Error::duplicate_field("type")); + return Err(de::Error::duplicate_field(new_tag.as_str())); } // use of `<'de: 'a, 'a> PropertyType<'a>: Deserialize<'de>` tag_type = Some(map.next_value::()?); } TagKind::Enum => { if tag_enum.is_some() { - return Err(de::Error::duplicate_field("enum")); + return Err(de::Error::duplicate_field(new_tag.as_str())); } tag_enum = Some(map.next_value::()?); } @@ -318,12 +332,20 @@ impl<'de: 'a, 'a> Deserialize<'de> for PropertyKind<'a> { } } if let Some(ref_value) = tag_ref { - vec.push((Content::String(TagKind::Ref.as_str().into()), ref_value)); + vec.push((Content::Str(TagKind::Ref.as_str()), ref_value)); return Ok((Tag::Ref, Content::Map(vec))); } + if let Some(oneof_value) = tag_oneof { + vec.push((Content::Str(TagKind::OneOf.as_str()), oneof_value)); + return Ok((Tag::OneOf, Content::Map(vec))); + } + if let Some(allof_value) = tag_allof { + vec.push((Content::Str(TagKind::AllOf.as_str()), allof_value)); + return Ok((Tag::AllOf, Content::Map(vec))); + } if let Some(enum_value) = tag_enum { if let Some(type_value) = tag_type { - vec.push((Content::String(TagKind::Enum.as_str().into()), enum_value)); + vec.push((Content::Str(TagKind::Enum.as_str()), enum_value)); return Ok((Tag::Enum(type_value), Content::Map(vec))); } else { return Err(de::Error::missing_field("type")); @@ -344,6 +366,22 @@ impl<'de: 'a, 'a> Deserialize<'de> for PropertyKind<'a> { let deserializer = ContentDeserializer::::new(content); match tag { Tag::Ref => RefProperty::deserialize(deserializer).map(PropertyKind::Ref), + Tag::OneOf => { + #[derive(Debug, Deserialize)] + struct OneOfProperty<'a> { + #[serde(rename = "oneOf", borrow = "'a")] + pub one_of: Vec>, + } + OneOfProperty::deserialize(deserializer).map(|c| PropertyKind::OneOf(c.one_of)) + } + Tag::AllOf => { + #[derive(Debug, Deserialize)] + struct AllOfProperty<'a> { + #[serde(rename = "allOf", borrow = "'a")] + pub all_of: Vec>, + } + AllOfProperty::deserialize(deserializer).map(|c| PropertyKind::AllOf(c.all_of)) + } Tag::Type(ty) => { let (ty, is_nullable) = match ty { PropertyType::Null => return Ok(PropertyKind::Value(TypedValue::Null)), @@ -533,6 +571,27 @@ mod tagged_enum { Ok(Content::Bool(value)) } + fn visit_i8(self, value: i8) -> Result + where + F: de::Error, + { + Ok(Content::I8(value)) + } + + fn visit_i16(self, value: i16) -> Result + where + F: de::Error, + { + Ok(Content::I16(value)) + } + + fn visit_i32(self, value: i32) -> Result + where + F: de::Error, + { + Ok(Content::I32(value)) + } + fn visit_i64(self, value: i64) -> Result where F: de::Error, @@ -540,6 +599,27 @@ mod tagged_enum { Ok(Content::I64(value)) } + fn visit_u8(self, value: u8) -> Result + where + F: de::Error, + { + Ok(Content::U8(value)) + } + + fn visit_u16(self, value: u16) -> Result + where + F: de::Error, + { + Ok(Content::U16(value)) + } + + fn visit_u32(self, value: u32) -> Result + where + F: de::Error, + { + Ok(Content::U32(value)) + } + fn visit_u64(self, value: u64) -> Result where F: de::Error, @@ -547,6 +627,13 @@ mod tagged_enum { Ok(Content::U64(value)) } + fn visit_f32(self, value: f32) -> Result + where + F: de::Error, + { + Ok(Content::F32(value)) + } + fn visit_f64(self, value: f64) -> Result where F: de::Error, @@ -554,6 +641,13 @@ mod tagged_enum { Ok(Content::F64(value)) } + fn visit_char(self, value: char) -> Result + where + F: de::Error, + { + Ok(Content::Char(value)) + } + fn visit_str(self, value: &str) -> Result where F: de::Error, @@ -561,6 +655,20 @@ mod tagged_enum { Ok(Content::String(value.into())) } + fn visit_borrowed_str(self, value: &'de str) -> Result + where + F: de::Error, + { + Ok(Content::Str(value)) + } + + fn visit_string(self, value: String) -> Result + where + F: de::Error, + { + Ok(Content::String(value)) + } + fn visit_bytes(self, value: &[u8]) -> Result where F: de::Error, @@ -568,6 +676,20 @@ mod tagged_enum { Ok(Content::ByteBuf(value.into())) } + fn visit_borrowed_bytes(self, value: &'de [u8]) -> Result + where + F: de::Error, + { + Ok(Content::Bytes(value)) + } + + fn visit_byte_buf(self, value: Vec) -> Result + where + F: de::Error, + { + Ok(Content::ByteBuf(value)) + } + fn visit_unit(self) -> Result where F: de::Error, @@ -632,6 +754,8 @@ mod tagged_enum { #[derive(Debug)] pub enum Tag { Ref, + OneOf, + AllOf, Type(PropertyType), Enum(PropertyType), None, @@ -640,18 +764,24 @@ mod tagged_enum { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TagKind { Ref, + OneOf, + AllOf, Type, Enum, } impl TagKind { pub const REF: &'static str = "$ref"; + pub const ONE_OF: &'static str = "oneOf"; + pub const ALL_OF: &'static str = "allOf"; pub const TYPE: &'static str = "type"; pub const ENUM: &'static str = "enum"; - pub const ALL: [Self; 3] = [Self::Ref, Self::Type, Self::Enum]; + pub const ALL: [Self; 5] = [Self::Ref, Self::OneOf, Self::AllOf, Self::Type, Self::Enum]; pub fn as_str(&self) -> &'static str { match self { TagKind::Ref => Self::REF, + TagKind::OneOf => Self::ONE_OF, + TagKind::AllOf => Self::ALL_OF, TagKind::Type => Self::TYPE, TagKind::Enum => Self::ENUM, } @@ -689,6 +819,33 @@ mod tagged_enum { .map(TagOrContent::Content) } + fn visit_i8(self, value: i8) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_i8(value) + .map(TagOrContent::Content) + } + + fn visit_i16(self, value: i16) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_i16(value) + .map(TagOrContent::Content) + } + + fn visit_i32(self, value: i32) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_i32(value) + .map(TagOrContent::Content) + } + fn visit_i64(self, value: i64) -> Result where F: de::Error, @@ -698,6 +855,33 @@ mod tagged_enum { .map(TagOrContent::Content) } + fn visit_u8(self, value: u8) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_u8(value) + .map(TagOrContent::Content) + } + + fn visit_u16(self, value: u16) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_u16(value) + .map(TagOrContent::Content) + } + + fn visit_u32(self, value: u32) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_u32(value) + .map(TagOrContent::Content) + } + fn visit_u64(self, value: u64) -> Result where F: de::Error, @@ -707,6 +891,15 @@ mod tagged_enum { .map(TagOrContent::Content) } + fn visit_f32(self, value: f32) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_f32(value) + .map(TagOrContent::Content) + } + fn visit_f64(self, value: f64) -> Result where F: de::Error, @@ -716,6 +909,15 @@ mod tagged_enum { .map(TagOrContent::Content) } + fn visit_char(self, value: char) -> Result + where + F: de::Error, + { + ContentVisitor::new() + .visit_char(value) + .map(TagOrContent::Content) + } + fn visit_str(self, value: &str) -> Result where F: de::Error, @@ -729,6 +931,32 @@ mod tagged_enum { } } + fn visit_borrowed_str(self, value: &'de str) -> Result + where + F: de::Error, + { + if let Some(t) = self.names.get(value) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_borrowed_str(value) + .map(TagOrContent::Content) + } + } + + fn visit_string(self, value: String) -> Result + where + F: de::Error, + { + if let Some(t) = self.names.get(value.as_str()) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_string(value) + .map(TagOrContent::Content) + } + } + fn visit_bytes(self, value: &[u8]) -> Result where F: de::Error, @@ -742,6 +970,32 @@ mod tagged_enum { } } + fn visit_borrowed_bytes(self, value: &'de [u8]) -> Result + where + F: de::Error, + { + if let Some((_, t)) = self.names.iter().find(|(k, _)| k.as_bytes() == value) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_borrowed_bytes(value) + .map(TagOrContent::Content) + } + } + + fn visit_byte_buf(self, value: Vec) -> Result + where + F: de::Error, + { + if let Some((_, t)) = self.names.iter().find(|(k, _)| k.as_bytes() == value) { + Ok(TagOrContent::Tag(*t)) + } else { + ContentVisitor::new() + .visit_byte_buf(value) + .map(TagOrContent::Content) + } + } + fn visit_unit(self) -> Result where F: de::Error, @@ -955,6 +1209,19 @@ mod tests { }, "additionalProperties": false, "title": "issues labeled event" + }, + "marketplace_purchase": { + "allOf": [ + { "$ref": "#/definitions/marketplace-purchase" }, + { + "type": "object", + "required": ["next_billing_date"], + "properties": { + "next_billing_date": { "type": "string", "format": "date-time" } + }, + "tsAdditionalProperties": false + } + ] } }, "oneOf": [ From 7d60b7657e414114dd51a3933a7ef9693ea5bf1c Mon Sep 17 00:00:00 2001 From: ylides Date: Thu, 31 Aug 2023 22:06:53 +0900 Subject: [PATCH 8/9] fix types --- type-generator/src/frontend/syntax.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs index 6ad323d..55fcd78 100644 --- a/type-generator/src/frontend/syntax.rs +++ b/type-generator/src/frontend/syntax.rs @@ -69,7 +69,7 @@ pub struct RefProperty<'a> { #[derive(Debug, Deserialize)] pub struct StringProperty { - pub format: StringFormat, + pub format: Option, } #[derive(Debug, Deserialize)] @@ -78,12 +78,14 @@ pub enum StringFormat { DateTime, #[serde(rename = "uri")] Uri, + #[serde(rename = "uri-template")] + UriTemplate, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ObjectProperty<'a> { - pub properties: ObjectProperties<'a>, + pub properties: Option>, pub required: Option>, pub title: Option<&'a str>, pub additional_properties: Option, @@ -458,7 +460,7 @@ impl<'de: 'a, 'a> Deserialize<'de> for PropertyKind<'a> { let mut vec = Vec::<&'a str>::with_capacity(seq.size_hint().unwrap_or_default()); if NULLABLE { - if let Some(s) = seq.next_element::>()? { + while let Some(s) = seq.next_element::>()? { if let Some(s) = s { vec.push(s); } else { From 21b8e8f3be42ffa287af91242fa352c4a47ae2a7 Mon Sep 17 00:00:00 2001 From: ylides Date: Fri, 1 Sep 2023 02:07:16 +0900 Subject: [PATCH 9/9] fix deser --- type-generator/src/frontend/syntax.rs | 64 ++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/type-generator/src/frontend/syntax.rs b/type-generator/src/frontend/syntax.rs index 55fcd78..844e0cd 100644 --- a/type-generator/src/frontend/syntax.rs +++ b/type-generator/src/frontend/syntax.rs @@ -46,6 +46,7 @@ mod schema_version { #[derive(Debug, Deserialize)] pub struct Property<'a> { + /// this field possibly contains a escape sequence `\"`, so it can't be `&str` pub description: Option, #[serde(flatten, borrow)] pub kind: PropertyKind<'a>, @@ -88,7 +89,7 @@ pub struct ObjectProperty<'a> { pub properties: Option>, pub required: Option>, pub title: Option<&'a str>, - pub additional_properties: Option, + pub additional_properties: Option>, } #[derive(Debug)] @@ -132,6 +133,54 @@ impl<'de: 'a, 'a> Deserialize<'de> for ObjectProperties<'a> { } } +#[derive(Debug)] +pub enum AdditionalProperties<'a> { + Boolean(bool), + Property(Box>), +} + +impl<'de: 'a, 'a> Deserialize<'de> for AdditionalProperties<'a> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AdditionalPropertiesVisitor<'a> { + value: PhantomData>, + } + + impl<'a> AdditionalPropertiesVisitor<'a> { + fn new() -> Self { + Self { value: PhantomData } + } + } + + impl<'de: 'a, 'a> Visitor<'de> for AdditionalPropertiesVisitor<'a> { + type Value = AdditionalProperties<'a>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an additional property declaration") + } + + fn visit_bool(self, b: bool) -> Result + where + E: de::Error, + { + Ok(AdditionalProperties::Boolean(b)) + } + + fn visit_map(self, map: A) -> Result + where + A: de::MapAccess<'de>, + { + PropertyKind::deserialize(de::value::MapAccessDeserializer::new(map)) + .map(|c| AdditionalProperties::Property(Box::new(c))) + } + } + + deserializer.deserialize_any(AdditionalPropertiesVisitor::new()) + } +} + #[derive(Debug, Deserialize)] pub struct ArrayProperty<'a> { #[serde(borrow = "'a")] @@ -1172,6 +1221,19 @@ mod tests { let _schema: SchemaVersion = serde_json::from_str(s).unwrap(); } #[test] + fn test_prop() { + let s = r#" + { + "type": "array", + "items": { + "type": "string" + }, + "description": "bar\"" + } + "#; + let _schema: Property = serde_json::from_str(s).unwrap(); + } + #[test] fn test_schema() { let s = r##" {