first commit
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate src/clarizen-api-types.ts from clarizen-schema.json."""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCHEMA = ROOT / "src" / "generated" / "clarizen-schema.json"
|
||||
OUT = ROOT / "src" / "clarizen-api-types.ts"
|
||||
|
||||
ENUMS = {
|
||||
"Operator": [
|
||||
"Equal", "NotEqual", "LessThan", "GreaterThan", "LessThanOrEqual",
|
||||
"GreaterThanOrEqual", "BeginsWith", "EndsWith", "Contains", "DoesNotContain",
|
||||
"In", "Like", "InRange", "NotInRange", "NotIn",
|
||||
],
|
||||
"Order": ["Ascending", "Descending"],
|
||||
"DatePeriod": [
|
||||
"ThisWeek", "ThisMonth", "ThisQuarter", "ThisYear",
|
||||
"NextWeek", "NextMonth", "NextQuarter", "NextYear",
|
||||
"LastWeek", "LastMonth", "LastQuarter", "LastYear",
|
||||
],
|
||||
"FunctionType": ["Count", "Max", "Min", "Sum", "Avg"],
|
||||
"LicenseType": [
|
||||
"Full", "Limited", "Email", "None", "TeamMember", "Social", "ExternalCollaborator",
|
||||
],
|
||||
"NewsFeedMode": ["Following", "All"],
|
||||
"TimesheetState": ["UnSubmitted", "PendingApproval", "Approved", "All"],
|
||||
"ErrorCode": [
|
||||
"EntityNotFound", "InvalidArgument", "MissingArgument", "InvalidOperation",
|
||||
"DuplicateKey", "InvalidField", "InvalidType", "FileNotFound", "VirusCheckFailed",
|
||||
"Unauthorized", "UnsupportedClient", "General", "Internal", "ValidationRuleError",
|
||||
"LoginFailure", "SessionTimeout", "Redirect", "InvalidQuery",
|
||||
"ExecutionThresholdExceeded", "TooManyRequests",
|
||||
],
|
||||
"FieldType": [
|
||||
"Boolean", "String", "Integer", "Long", "Double", "DateTime", "Date",
|
||||
"Entity", "Duration", "Money",
|
||||
],
|
||||
"PresentationType": [
|
||||
"Text", "Numeric", "Date", "Time", "Checkbox", "TextArea", "Currency",
|
||||
"Duration", "ReferenceToObject", "PickList", "Url", "Percent", "Other",
|
||||
],
|
||||
"RecipientType": ["To", "CC"],
|
||||
"AccessType": ["Public", "Private"],
|
||||
"TriggerType": [
|
||||
"Create", "CreateOrEdit", "Delete", "CreateOrEditWithPreviousValueNotEqual",
|
||||
],
|
||||
"HttpMethod": ["DELETE", "GET", "POST", "PUT"],
|
||||
}
|
||||
|
||||
STUB_TYPES = {
|
||||
"ApplyOnMembersResult": "string",
|
||||
"IFormat": "Record<string, JsonValue>",
|
||||
"RoleType": "string",
|
||||
}
|
||||
|
||||
PRIMITIVE_MAP = {
|
||||
"string": "string",
|
||||
"boolean": "boolean",
|
||||
"integer": "number",
|
||||
"long": "number",
|
||||
"double": "number",
|
||||
"datetime": "string",
|
||||
"date": "string",
|
||||
"type": "Record<string, JsonValue>",
|
||||
"json object": "Record<string, JsonValue>",
|
||||
"object": "JsonValue",
|
||||
"entityid": "EntityId",
|
||||
}
|
||||
|
||||
|
||||
def pascal(name: str) -> str:
|
||||
if not name:
|
||||
return name
|
||||
if name[0].isupper():
|
||||
return name.replace(" ", "")
|
||||
return name[0].upper() + name[1:]
|
||||
|
||||
|
||||
ARRAY_PROP_NAMES = frozenset({
|
||||
"fields", "groupby", "orders", "relations", "aggregations",
|
||||
"feeditemoptions", "workitems", "validstates",
|
||||
})
|
||||
|
||||
|
||||
def ts_prop_type(prop: dict) -> str:
|
||||
pname = prop.get("name", "").rstrip("*").lower()
|
||||
href = prop.get("typeHref") or ""
|
||||
if href:
|
||||
tail = href.rstrip("/").split("/")[-1]
|
||||
tail = pascal(tail)
|
||||
if tail in ("ListOf_String", "Array_String"):
|
||||
return "string[]"
|
||||
if pname in ARRAY_PROP_NAMES or tail == "FieldAggregation":
|
||||
if tail == "FieldAggregation":
|
||||
return "FieldAggregation[]"
|
||||
if tail == "FieldDescription":
|
||||
return "FieldDescription[]"
|
||||
if tail == "RelationDescription":
|
||||
return "RelationDescription[]"
|
||||
if tail == "OrderBy":
|
||||
return "OrderBy[]"
|
||||
if tail == "Relation":
|
||||
return "Relation[]"
|
||||
return f"{tail}[]"
|
||||
return tail
|
||||
raw = prop.get("type", "JsonValue").lower()
|
||||
raw = re.sub(r"\(.*\)", "", raw).strip()
|
||||
if raw.endswith("[]"):
|
||||
inner = PRIMITIVE_MAP.get(raw[:-2], pascal(raw[:-2]))
|
||||
return f"{inner}[]"
|
||||
return PRIMITIVE_MAP.get(raw, "JsonValue")
|
||||
|
||||
|
||||
def emit_enum(name: str, values: list[str]) -> str:
|
||||
lines = [f"export type {name} ="]
|
||||
for v in values:
|
||||
lines.append(f' | "{v}"')
|
||||
lines[-1] = lines[-1] + ";"
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def prop_optional(prop_name: str) -> str:
|
||||
return "" if prop_name.endswith("*") else "?"
|
||||
|
||||
|
||||
def clean_prop_name(prop_name: str) -> str:
|
||||
return prop_name.rstrip("*")
|
||||
|
||||
|
||||
def emit_interface(name: str, variant: dict) -> str:
|
||||
props = variant.get("properties", [])
|
||||
if not props and "(enum)" in variant.get("variant", ""):
|
||||
return ""
|
||||
lines = [f"export interface {name} {{"]
|
||||
for p in props:
|
||||
pname = clean_prop_name(p["name"])
|
||||
lines.append(f" {pname}{prop_optional(p['name'])}: {ts_prop_type(p)};")
|
||||
lines.append("}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
data = json.loads(SCHEMA.read_text())
|
||||
parsed = data["parsed"]
|
||||
|
||||
out: list[str] = [
|
||||
"// Auto-generated from Clarizen REST API V2.0 type docs. Run: npm run generate:types",
|
||||
'import type { JsonValue } from "./types.js";',
|
||||
"",
|
||||
"export type EntityId = string;",
|
||||
"",
|
||||
]
|
||||
|
||||
for enum_name, values in ENUMS.items():
|
||||
out.append(emit_enum(enum_name, values))
|
||||
out.append("")
|
||||
|
||||
# Condition union (manual structure from variants)
|
||||
out.append("export interface AndCondition { and: Condition[]; }")
|
||||
out.append("export interface OrCondition { or: Condition[]; }")
|
||||
out.append("export interface NotCondition { not: Condition; }")
|
||||
out.append("export interface CompareCondition {")
|
||||
out.append(" _type?: \"Compare\";")
|
||||
out.append(" leftExpression: Expression;")
|
||||
out.append(" operator: ComparisonOperator;")
|
||||
out.append(" rightExpression: Expression;")
|
||||
out.append("}")
|
||||
out.append("export interface CzqlCondition {")
|
||||
out.append(" text: string;")
|
||||
out.append(" parameters?: Parameters;")
|
||||
out.append("}")
|
||||
out.append(
|
||||
"export type Condition = AndCondition | OrCondition | NotCondition | CompareCondition | CzqlCondition;"
|
||||
)
|
||||
out.append("")
|
||||
|
||||
# Expression union
|
||||
out.append("export interface FieldExpression { fieldName: string; }")
|
||||
out.append("export interface ConstantExpression { value: JsonValue; }")
|
||||
out.append("export interface ConstantListExpression { values: JsonValue[]; }")
|
||||
out.append("export interface QueryExpression { query: Query; }")
|
||||
out.append("export interface PredefinedDateRangeExpression {")
|
||||
out.append(" value?: DatePeriod;")
|
||||
out.append(" dateRangeValue?: DatePeriod;")
|
||||
out.append("}")
|
||||
out.append(
|
||||
"export type Expression = FieldExpression | ConstantExpression | ConstantListExpression | QueryExpression | PredefinedDateRangeExpression;"
|
||||
)
|
||||
out.append("")
|
||||
|
||||
# Single-variant interfaces from schema
|
||||
skip = {
|
||||
"Condition", "Expression", "Operator", "Order", "Query",
|
||||
"ListOf_String", "Array_String", "EntityId",
|
||||
}
|
||||
for type_name, stub in STUB_TYPES.items():
|
||||
out.append(f"export type {type_name} = {stub};")
|
||||
out.append("")
|
||||
|
||||
for type_name in sorted(parsed.keys()):
|
||||
if type_name in skip or type_name in STUB_TYPES:
|
||||
continue
|
||||
info = parsed[type_name]
|
||||
if len(info["variants"]) != 1:
|
||||
continue
|
||||
v = info["variants"][0]
|
||||
if "(enum)" in v.get("variant", ""):
|
||||
continue
|
||||
iface = emit_interface(type_name, v)
|
||||
if iface:
|
||||
out.append(iface)
|
||||
out.append("")
|
||||
|
||||
# Query variants
|
||||
query_info = parsed.get("Query", {})
|
||||
query_names: list[str] = []
|
||||
for v in query_info.get("variants", []):
|
||||
variant_key = v["variant"]
|
||||
iface_name = pascal(variant_key)
|
||||
if iface_name == "CZQLQuery":
|
||||
iface_name = "CzqlQuery"
|
||||
props = v.get("properties", [])
|
||||
lines = [f"export interface {iface_name} {{"]
|
||||
for p in props:
|
||||
pname = clean_prop_name(p["name"])
|
||||
lines.append(f" {pname}{prop_optional(p['name'])}: {ts_prop_type(p)};")
|
||||
lines.append("}")
|
||||
out.append("\n".join(lines))
|
||||
out.append("")
|
||||
query_names.append(iface_name)
|
||||
|
||||
out.append(f"export type Query = {' | '.join(query_names)};")
|
||||
out.append("")
|
||||
|
||||
# Shared query body (entityQuery variant) for EntityQuery operation
|
||||
out.append("export type EntityQueryBody = EntityQuery;")
|
||||
out.append("")
|
||||
|
||||
out.append("export type ComparisonOperator = Operator;")
|
||||
out.append("export type SortOrder = Order;")
|
||||
out.append("export type ConditionParameters = Parameters;")
|
||||
|
||||
text = "\n".join(out) + "\n"
|
||||
OUT.write_text(text)
|
||||
print(f"Wrote {OUT} ({len(text)} bytes)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user