From b658222f540ae107c458e9b5f9902140c3863941 Mon Sep 17 00:00:00 2001 From: Nokome Bentley Date: Fri, 4 Jun 2021 17:02:58 +1200 Subject: [PATCH] fix(Node): Expand the `Node` union type to include all entity types --- python/stencila/schema/json.py | 4 +- python/stencila/schema/types.py | 4 +- r/R/types.R | 4 +- rust/src/types.rs | 99 +++++++++++++++++++++++++++++--- schema/InlineContent.schema.yaml | 5 +- schema/Node.schema.yaml | 10 ++-- ts/bindings/rust.ts | 6 ++ ts/schema.ts | 17 ++++++ 8 files changed, 126 insertions(+), 23 deletions(-) diff --git a/python/stencila/schema/json.py b/python/stencila/schema/json.py index 9dbffad4ac..ca7454b185 100644 --- a/python/stencila/schema/json.py +++ b/python/stencila/schema/json.py @@ -13,7 +13,7 @@ from .types import Node, Entity -def decode(serialized: str) -> typing.Union[dict, Node]: +def decode(serialized: str) -> Node: """Decode JSON as a `Node`""" node = json.loads(serialized) return dict_decode(node) if isinstance(node, dict) else node @@ -24,7 +24,7 @@ def encode(node: Node) -> str: return json.dumps(node, default=object_encode, indent=2) -def dict_decode(node_dict: dict) -> typing.Union[dict, Entity]: +def dict_decode(node_dict: dict) -> Node: """Convert a dictionary to an `Entity` node (if it has a `type` item).""" if "type" not in node_dict: return node_dict diff --git a/python/stencila/schema/types.py b/python/stencila/schema/types.py index 087344c1ee..22290811ed 100644 --- a/python/stencila/schema/types.py +++ b/python/stencila/schema/types.py @@ -4412,9 +4412,9 @@ class CitationIntentEnumeration(Enum): """ -Union type for all valid nodes. +Union type for all schema nodes, including primitives and entities """ -Node = Union["Entity", None, bool, int, float, str, Dict[str, Any], Array[Any]] +Node = Union["ArrayValidator", "Article", "AudioObject", "BooleanValidator", "Brand", "CitationIntentEnumeration", "Cite", "CiteGroup", "Claim", "Code", "CodeBlock", "CodeChunk", "CodeError", "CodeExpression", "CodeFragment", "Collection", "Comment", "ConstantValidator", "ContactPoint", "CreativeWork", "Datatable", "DatatableColumn", "Date", "DefinedTerm", "Delete", "Emphasis", "EnumValidator", "Enumeration", "Figure", "Function", "Grant", "Heading", "ImageObject", "Include", "IntegerValidator", "Link", "List", "ListItem", "Mark", "Math", "MathBlock", "MathFragment", "MediaObject", "MonetaryGrant", "NontextualAnnotation", "Note", "NumberValidator", "Organization", "Paragraph", "Parameter", "Periodical", "Person", "PostalAddress", "Product", "PropertyValue", "PublicationIssue", "PublicationVolume", "Quote", "QuoteBlock", "Review", "SoftwareApplication", "SoftwareEnvironment", "SoftwareSession", "SoftwareSourceCode", "StringValidator", "Strong", "Subscript", "Superscript", "Table", "TableCell", "TableRow", "ThematicBreak", "Thing", "TupleValidator", "Validator", "Variable", "VideoObject", "VolumeMount", None, bool, int, float, str, Dict[str, Any], Array[Any]] """ diff --git a/r/R/types.R b/r/R/types.R index ade35ab099..628c75963d 100644 --- a/r/R/types.R +++ b/r/R/types.R @@ -4508,11 +4508,11 @@ MathTypes <- Union(Math, MathBlock, MathFragment) MediaObjectTypes <- Union(MediaObject, AudioObject, ImageObject, VideoObject) -#' Union type for all valid nodes. +#' Union type for all schema nodes, including primitives and entities #' #' @return A `list` of class `Union` describing valid subtypes of this type #' @export -Node <- Union(Entity, "NULL", "logical", "numeric", "character", "list", Array(Any())) +Node <- Union(ArrayValidator, Article, AudioObject, BooleanValidator, Brand, CitationIntentEnumeration, Cite, CiteGroup, Claim, Code, CodeBlock, CodeChunk, CodeError, CodeExpression, CodeFragment, Collection, Comment, ConstantValidator, ContactPoint, CreativeWork, Datatable, DatatableColumn, Date, DefinedTerm, Delete, Emphasis, EnumValidator, Enumeration, Figure, Function, Grant, Heading, ImageObject, Include, IntegerValidator, Link, List, ListItem, Mark, Math, MathBlock, MathFragment, MediaObject, MonetaryGrant, NontextualAnnotation, Note, NumberValidator, Organization, Paragraph, Parameter, Periodical, Person, PostalAddress, Product, PropertyValue, PublicationIssue, PublicationVolume, Quote, QuoteBlock, Review, SoftwareApplication, SoftwareEnvironment, SoftwareSession, SoftwareSourceCode, StringValidator, Strong, Subscript, Superscript, Table, TableCell, TableRow, ThematicBreak, Thing, TupleValidator, Validator, Variable, VideoObject, VolumeMount, "NULL", "logical", "numeric", "character", "list", Array(Any())) #' All type schemas that are derived from Thing diff --git a/rust/src/types.rs b/rust/src/types.rs index d0c3fff909..61524ec369 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -273,7 +273,8 @@ pub struct CodeExpression { pub meta: Option, /// The value of the expression when it was last evaluated. - pub output: Option, + #[serde(skip)] + pub output: Option>, /// The programming language of the code. pub programming_language: Option, @@ -1593,7 +1594,8 @@ pub struct ConstantValidator { pub meta: Option, /// The value that the node must have. - pub value: Option, + #[serde(skip)] + pub value: Option>, } impl_type!(ConstantValidator); @@ -2125,7 +2127,8 @@ pub struct ListItem { pub is_checked: Option, /// The item represented by this list item. - pub item: Option, + #[serde(skip)] + pub item: Option>, /// Metadata associated with this item. pub meta: Option, @@ -2462,7 +2465,8 @@ pub struct Variable { pub validator: Option, /// The value of the variable. - pub value: Option, + #[serde(skip)] + pub value: Option>, } impl_type!(Variable); @@ -2480,7 +2484,8 @@ pub struct Parameter { pub name: String, /// The default value of the parameter. - pub default: Option, + #[serde(skip)] + pub default: Option>, /// The identifier for this item. pub id: Option, @@ -2504,7 +2509,8 @@ pub struct Parameter { pub validator: Option, /// The value of the variable. - pub value: Option, + #[serde(skip)] + pub value: Option>, } impl_type!(Parameter); @@ -4969,11 +4975,88 @@ pub enum MediaObjectTypes { VideoObject(VideoObject), } -/// Union type for all valid nodes. +/// Union type for all schema nodes, including primitives and entities #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum Node { - Entity(Entity), + ArrayValidator(ArrayValidator), + Article(Article), + AudioObject(AudioObject), + BooleanValidator(BooleanValidator), + Brand(Brand), + CitationIntentEnumeration(CitationIntentEnumeration), + Cite(Cite), + CiteGroup(CiteGroup), + Claim(Claim), + Code(Code), + CodeBlock(CodeBlock), + CodeChunk(CodeChunk), + CodeError(CodeError), + CodeExpression(CodeExpression), + CodeFragment(CodeFragment), + Collection(Collection), + Comment(Comment), + ConstantValidator(ConstantValidator), + ContactPoint(ContactPoint), + CreativeWork(CreativeWork), + Datatable(Datatable), + DatatableColumn(DatatableColumn), + Date(Date), + DefinedTerm(DefinedTerm), + Delete(Delete), + Emphasis(Emphasis), + EnumValidator(EnumValidator), + Enumeration(Enumeration), + Figure(Figure), + Function(Function), + Grant(Grant), + Heading(Heading), + ImageObject(ImageObject), + Include(Include), + IntegerValidator(IntegerValidator), + Link(Link), + List(List), + ListItem(ListItem), + Mark(Mark), + Math(Math), + MathBlock(MathBlock), + MathFragment(MathFragment), + MediaObject(MediaObject), + MonetaryGrant(MonetaryGrant), + NontextualAnnotation(NontextualAnnotation), + Note(Note), + NumberValidator(NumberValidator), + Organization(Organization), + Paragraph(Paragraph), + Parameter(Parameter), + Periodical(Periodical), + Person(Person), + PostalAddress(PostalAddress), + Product(Product), + PropertyValue(PropertyValue), + PublicationIssue(PublicationIssue), + PublicationVolume(PublicationVolume), + Quote(Quote), + QuoteBlock(QuoteBlock), + Review(Review), + SoftwareApplication(SoftwareApplication), + SoftwareEnvironment(SoftwareEnvironment), + SoftwareSession(SoftwareSession), + SoftwareSourceCode(SoftwareSourceCode), + StringValidator(StringValidator), + Strong(Strong), + Subscript(Subscript), + Superscript(Superscript), + Table(Table), + TableCell(TableCell), + TableRow(TableRow), + ThematicBreak(ThematicBreak), + Thing(Thing), + TupleValidator(TupleValidator), + Validator(Validator), + Variable(Variable), + VideoObject(VideoObject), + VolumeMount(VolumeMount), Null, Bool(Bool), Integer(Integer), diff --git a/schema/InlineContent.schema.yaml b/schema/InlineContent.schema.yaml index 4f197da27f..7a74294d3c 100644 --- a/schema/InlineContent.schema.yaml +++ b/schema/InlineContent.schema.yaml @@ -3,11 +3,8 @@ category: text status: stable description: Union type for valid inline content. $comment: | - Note that this definition currently does not include - `array` and `object` nodes (which are included in `Node`). - This seems incongruent, and may be changed in the future. The order of these types is important because it determines the - order of attempted coercion (particularly important for primitive types). + order of attempted coercion (this is particularly important for primitive types). anyOf: # Entity types in alphabetical order - $ref: AudioObject diff --git a/schema/Node.schema.yaml b/schema/Node.schema.yaml index 6e1560939b..70bc7358c2 100644 --- a/schema/Node.schema.yaml +++ b/schema/Node.schema.yaml @@ -1,18 +1,18 @@ title: Node category: other status: unstable -description: Union type for all valid nodes. +description: Union type for all schema nodes, including primitives and entities $comment: | + The entity types in this union are automatically inserted during the build. The order of these types is important because it determines the - order of attempted coercion (ie. parsing and reshaping to arrays) + order of attempted coercion (ie. parsing and reshaping to arrays). + Array should come last to avoid single items (e.g. a single string) + being coerced into an array. anyOf: - - $ref: Entity - type: 'null' - type: boolean - type: integer - type: number - type: string - type: object - # Array should come last to avoid single items (e.g. a single string) - # being coerced into an array. - type: array diff --git a/ts/bindings/rust.ts b/ts/bindings/rust.ts index 50e453f9a0..868789ea21 100644 --- a/ts/bindings/rust.ts +++ b/ts/bindings/rust.ts @@ -50,9 +50,15 @@ const pointerProperties = [ 'Organization.parentOrganization', 'ImageObject.publisher', // recursive because publisher has `logo` 'ImageObject.thumbnail', + 'ListItem.item', 'Comment.parentItem', 'ArrayValidator.contains', 'ArrayValidator.itemsValidator', + 'ConstantValidator.value', + 'CodeExpression.output', + 'Parameter.default', + 'Parameter.value', + 'Variable.value', ] /** diff --git a/ts/schema.ts b/ts/schema.ts index fe32b5dedf..5b6114143c 100644 --- a/ts/schema.ts +++ b/ts/schema.ts @@ -94,6 +94,9 @@ export async function build(cleanup = true): Promise { // Process each of the schemas schemata.forEach((schema) => processSchema(schemas, schema)) + // Update the `Node` union schema` + updateNodeSchema(schemas) + // Generate additional schemas addTypesSchemas(schemas) @@ -440,6 +443,20 @@ const parentSchema = ( return parent } +/** + * Add all entity types to the `Node` union schema. + */ +const updateNodeSchema = (schemas: Map): void => { + const entitySchema = schemas.get('Entity') as JsonSchema + const entityRefs = (entitySchema.descendants ?? []).map((descendant) => ({ + $ref: `${descendant}.schema.json`, + })) + + const nodeSchema = schemas.get('Node') as JsonSchema + nodeSchema.anyOf = [...(entityRefs ?? []), ...(nodeSchema.anyOf ?? [])] + schemas.set('Node', nodeSchema) +} + /** * Add `*Types` schemas to the map of schemas which * are the union (`anyOf`) of any descendant types