Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,545 changes: 1,545 additions & 0 deletions examples/Hello Wall/procedural-window-frame.ifcx

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions schema/ifcx.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ enum DataType
Array: "Array",
Object: "Object",
Reference: "Reference",
Blob: "Blob"
Blob: "Blob",
Union: "Union"
}

model EnumRestrictions
Expand All @@ -58,16 +59,23 @@ model ObjectRestrictions
values: Record<IfcxValueDescription>;
}

model UnionRestrictions
{
values: IfcxValueDescription[];
}

model IfcxValueDescription
{
dataType: DataType;
dataType?: DataType;
optional?: boolean;
inherits?: string[];
quantityKind?: QuantityKind;

enumRestrictions?: EnumRestrictions;
arrayRestrictions?: ArrayRestrictions;
objectRestrictions?: ObjectRestrictions;
unionRestrictions?: UnionRestrictions;
ref?: string;
}

model IfcxSchema
Expand Down
23 changes: 19 additions & 4 deletions schema/out/@typespec/openapi3/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ components:
- Object
- Reference
- Blob
- Union
EnumRestrictions:
type: object
required:
Expand Down Expand Up @@ -65,13 +66,16 @@ components:
type: object
required:
- id
- version
- ifcxVersion
- dataVersion
- author
- timestamp
properties:
id:
type: string
version:
ifcxVersion:
type: string
dataVersion:
type: string
author:
type: string
Expand Down Expand Up @@ -108,8 +112,6 @@ components:
$ref: '#/components/schemas/IfcxValueDescription'
IfcxValueDescription:
type: object
required:
- dataType
properties:
dataType:
$ref: '#/components/schemas/DataType'
Expand All @@ -127,6 +129,10 @@ components:
$ref: '#/components/schemas/ArrayRestrictions'
objectRestrictions:
$ref: '#/components/schemas/ObjectRestrictions'
unionRestrictions:
$ref: '#/components/schemas/UnionRestrictions'
ref:
type: string
ImportNode:
type: object
required:
Expand Down Expand Up @@ -162,6 +168,15 @@ components:
- Energy
- Power
- Volume
UnionRestrictions:
type: object
required:
- values
properties:
values:
type: array
items:
$ref: '#/components/schemas/IfcxValueDescription'
code:
type: string
pattern: </[A-Za-z0-9]+>
Expand Down
12 changes: 9 additions & 3 deletions schema/out/ts/ifcx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface components {
value: components["schemas"]["IfcxValueDescription"];
};
/** @enum {string} */
DataType: "Real" | "Boolean" | "Integer" | "String" | "DateTime" | "Enum" | "Array" | "Object" | "Reference" | "Blob";
DataType: "Real" | "Boolean" | "Integer" | "String" | "DateTime" | "Enum" | "Array" | "Object" | "Reference" | "Blob" | "Union";
EnumRestrictions: {
options: string[];
};
Expand All @@ -27,7 +27,8 @@ export interface components {
};
IfcxHeader: {
id: string;
version: string;
ifcxVersion: string;
dataVersion: string;
author: string;
timestamp: string;
};
Expand All @@ -48,13 +49,15 @@ export interface components {
value: components["schemas"]["IfcxValueDescription"];
};
IfcxValueDescription: {
dataType: components["schemas"]["DataType"];
dataType?: components["schemas"]["DataType"];
optional?: boolean;
inherits?: string[];
quantityKind?: components["schemas"]["QuantityKind"];
enumRestrictions?: components["schemas"]["EnumRestrictions"];
arrayRestrictions?: components["schemas"]["ArrayRestrictions"];
objectRestrictions?: components["schemas"]["ObjectRestrictions"];
unionRestrictions?: components["schemas"]["UnionRestrictions"];
ref?: string;
};
ImportNode: {
uri: string;
Expand All @@ -67,6 +70,9 @@ export interface components {
};
/** @enum {string} */
QuantityKind: "Plane angle" | "Thermodynamic temperature" | "Electric current" | "Time" | "Frequency" | "Mass" | "Length" | "Linear velocity" | "Force" | "Pressure" | "Area" | "Energy" | "Power" | "Volume";
UnionRestrictions: {
values: components["schemas"]["IfcxValueDescription"][];
};
code: string;
path: string;
};
Expand Down
63 changes: 62 additions & 1 deletion src/ifcx-core/schema/schema-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,44 @@ export class SchemaValidationError extends Error

}

function TryValidateAttributeValue(desc: IfcxValueDescription, value: any, path: string, schemas: {[key: string]: IfcxSchema})
{
try {
ValidateAttributeValue(desc, value, path, schemas);
return null;
} catch (e) {
return e;
}
}

function stringifyWithDepthLimit(value, maxDepth=3) {
const seen = new WeakSet();

function traverse(val, depth) {
if (depth > maxDepth) {
return Array.isArray(val) ? [] : (typeof val === "object" && val !== null ? {} : val);
}

if (val && typeof val === "object") {
if (seen.has(val)) return; // prevent cycles
seen.add(val);

if (Array.isArray(val)) {
return val.map(v => traverse(v, depth + 1));
} else {
const out = {};
for (const [k, v] of Object.entries(val)) {
out[k] = traverse(v, depth + 1);
}
return out;
}
}
return val;
}

return JSON.stringify(traverse(value, 1));
}

function ValidateAttributeValue(desc: IfcxValueDescription, value: any, path: string, schemas: {[key: string]: IfcxSchema})
{
if (desc.optional && value === undefined)
Expand All @@ -26,6 +64,21 @@ function ValidateAttributeValue(desc: IfcxValueDescription, value: any, path: st
});
}

while (desc.ref) {
const oldRef = desc.ref;
console.log(oldRef, ...Object.keys(value));
if (typeof value === "object" && Object.keys(value).length == 1 && Object.keys(value)[0] === oldRef) {
// @todo this is a nasty hack out of utter laziness and uncertainty whether to use a
// *tagged* union or not, but for now, when (union) refs are observed and similar keys
// are found in the *data*, then the data is traversed accordingly.
value = Object.values(value)[0];
}
desc = schemas[desc.ref].value;
if (!desc) {
throw new SchemaValidationError(`Reference to undefined schema type ${oldRef}`);
}
}

if (desc.dataType === "Boolean")
{
if (typeof value !== "boolean")
Expand Down Expand Up @@ -95,7 +148,7 @@ function ValidateAttributeValue(desc: IfcxValueDescription, value: any, path: st

if (!hasOwn)
{
throw new SchemaValidationError(`Expected "${value}" to have key ${key}`);
throw new SchemaValidationError(`Expected "${typeof value === 'object' ? JSON.stringify(value) : value}" to have key ${key}`);
}
ValidateAttributeValue(desc.objectRestrictions!.values[key], value[key], path + "." + key, schemas);
})
Expand All @@ -111,6 +164,14 @@ function ValidateAttributeValue(desc: IfcxValueDescription, value: any, path: st
ValidateAttributeValue(desc.arrayRestrictions!.value, entry, path + ".<array>.", schemas);
})
}
else if (desc.dataType === "Union")
{
const nonFailures = desc.unionRestrictions ? desc.unionRestrictions.values.map(v => TryValidateAttributeValue(v, value, path + ".<union>.", schemas)).filter(v => v === null) : [];
console.log("Union", stringifyWithDepthLimit(value), ...(desc.unionRestrictions ? desc.unionRestrictions.values.map(a => stringifyWithDepthLimit(a)) : []), nonFailures.length)
if (nonFailures.length == 0) {
throw new SchemaValidationError(`Expected "${stringifyWithDepthLimit(value)}" to be match any of the types ${stringifyWithDepthLimit(desc.unionRestrictions?.values)}`);
}
}
else
{
throw new SchemaValidationError(`Unexpected datatype ${desc.dataType}`);
Expand Down
3 changes: 2 additions & 1 deletion src/utils/python/externalise_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
obj["imports"] = []

prefix = 'https://ifcx.dev'
local_path = '../../../../ifcx.dev/'

mapping = {
'bsi::ifc::prop': '@standards.buildingsmart.org/ifc/core/[email protected]',
Expand Down Expand Up @@ -37,7 +38,7 @@ def w(path, name, schema):
for k, v in list(obj["schemas"].items()):
for m, n in mapping.items():
if k.startswith(m):
w('../../../web/' + n, k, v)
w(local_path + n, k, v)
del obj["schemas"][k]
if m not in (x['uri'] for x in obj["imports"]):
obj["imports"].append({
Expand Down
Loading