Skip to content

feat(typescript): generate toXml()/fromXml()/builders for xml-encoded object types (TwiML) - #17785

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1789674767-ts-xml-twiml
Open

devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1789674767-ts-xml-twiml

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Description

Linear ticket: Refs (Twilio TwiML)

TypeScript counterpart of #17734 (Python) and #17755 (Java), on top of the IR xml metadata from #17732. Object types that carry encoding.xml (e.g. TwiML schemas imported from an OpenAPI xml object) are generated as classes instead of interfaces, with serialization, strict parsing and Twilio-style fluent builders. Non-xml types are untouched and the xml runtime is only copied into an SDK when an xml type references it.

Generated shape (TwiML Response):

const response = SeedApi.Response.builder();
response.say("Hello", { voice: "Polly.Joanna" }).addBreak({ time: "1s" });   // `break` is reserved -> addBreak
response.dial({ callerId: "+1555" }).addNumber("+1555");                    // Dial has a `number` text field -> addNumber
response.hangup();
response.toXml();
// <?xml version="1.0" encoding="UTF-8"?><Response><Say voice="Polly.Joanna">Hello<break time="1s" /></Say>...

SeedApi.Response.fromXml(xml);                 // -> Response (immutable model)
SeedApi.Response.Builder.fromXml(xml).build(); // twilio-node shape
  • class X { fields...; toXml(); toString(); static fromXml(xml); static builder() } + namespace X { interface Fields; class Builder }.
  • Builder child methods are derived from the property's child union (Response.Children.Item = Say | Dial | ...); accept a Partial<Fields> or an existing model/builder (xmlBuild/xmlBuildAll). Naming rule B, same as Python/Java: plain camelCase, add prefix on collision with a property/method or when the name is unsafe/reserved.
  • Attributes, text body, nested/union children, repeated children, wrapped lists, listSeparator on attributes/text, namespace + prefix, escaping; xml declaration emitted only for document roots (types not used as a child of another xml type — TypeResolver.isXmlChildType).
  • Undeclared attributes → additionalAttributes: Record<string,string>, undeclared children → additionalChildren: XmlElement[]; both re-emitted by toXml() so documents round-trip. Known limitation shared with Python/Java: mixed-content ordering of unknown children is not preserved (declared children first).
  • Parsing is strict: malformed xml, wrong root, multiple roots, trailing content, duplicate/unquoted attributes, invalid scalars/enums all throw XmlParseError; <!DOCTYPE> and other declarations are rejected (no entity expansion beyond the 5 predefined + numeric refs).
  • Serde layer: no zurg schemas are generated for xml-encoded types or types that transitively reference one (TypeResolver.isXmlDependentType, via IR referencedTypes which is already transitive); non-xml types in the same API still get schemas (PlainObject in the serde-layer seed output).

Changes Made

  • utils/core-utilities/src/core/xml/ — new as-is runtime: parse.ts (small secure parser → XmlNode), serialize.ts (serializeXmlElement), read.ts (typed readers xmlAttribute/xmlText/xmlChild(ren)/xmlEnum/...), XmlElement.ts (generic node), builder.ts (XmlBuilder, xmlBuild, xmlBuildAll); + tests/unit/xml/xml.test.ts copied into generated SDKs.
  • utils/commons: core-utilities/Xml.ts (manifest src/core/xml/**, copied on demand) and codegen-utils/xmlTypes.ts (getXmlEncoding, getXmlValueShape, getXmlChildObjectTypes, getXmlChildTypeIds, isXmlDependentType).
  • utils/resolvers/TypeResolver.ts: getTypeDeclarationById, isXmlChildType, isXmlDependentType.
  • model/type-generator: new XmlObjectGenerator.ts; GeneratedObjectTypeImpl emits the class + module statements when xml is set; TypeGenerator threads xml/isXmlRoot/useBigInt.
  • sdk/generator: TypeContextImpl passes xml metadata + root detection; SdkGenerator.generateTypeSchemas skips xml-dependent types.
  • Fixture test-definitions/fern/apis/ts-xml-twiml (TwiML subset: Response/Say/Break/Dial/Number/Pause/Hangup, namespace+prefix, wrapped <Numbers>, list attrs, enum list attrs, lowercase tags, 256-attribute Wide, non-xml PlainObject) registered in seed/ts-sdk/seed.yml as no-custom-config and serde-layer; seed output committed.
  • Changelog generators/typescript/sdk/changes/unreleased/feat-xml-encoded-types.yml.
  • Updated README.md generator (not applicable)

Testing

  • Unit tests added/updated — model/type-generator/src/__test__/XmlObjectGenerator.test.ts (root with text/attrs/children, nested element with namespace/wrapped list/separator/add-prefixed collision, non-xml stays an interface) + core-utilities/tests/unit/xml/xml.test.ts (8 runtime tests, shipped into generated SDKs).
  • pnpm turbo run compile lint:eslint test --filter "./generators/typescript/**" — 63/63 tasks green; biome check clean.
  • seed test --generator ts-sdk --fixture ts-xml-twiml --local — 2/2 (no-custom-config, serde-layer); generated SDK tsc --noEmit clean.
  • Round-trip test against the generated fixture SDK (14/14): docs-style builders, model instances as children, fromXml / Builder.fromXml, unknown attrs + children preserved, Wide, malformed / wrong-root / bad scalar / bad enum / DOCTYPE / multiple-root rejection, XmlElement escape hatch.
  • Regression: seed test --generator ts-sdk --fixture exhaustive --local — 26/26, no output changes attributable to this PR.

Link to Devin session: https://app.devin.ai/sessions/311fe13ae5434245ada665a9b824809b
Open in Devin Desktop: https://app.devin.ai/desktop/session/311fe13ae5434245ada665a9b824809b?variant=devin


Devin Review

… object types (TwiML)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

nitpickybot[bot]

This comment was marked as resolved.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 6 potential issues.

3 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread generators/typescript/utils/commons/src/codegen-utils/xmlTypes.ts
Comment thread generators/typescript/utils/commons/src/codegen-utils/xmlTypes.ts
Comment thread generators/typescript/utils/core-utilities/src/core/xml/read.ts
Comment thread generators/typescript/utils/core-utilities/src/core/xml/parse.ts Outdated
Comment thread generators/typescript/model/type-generator/src/object/XmlObjectGenerator.ts Outdated
Comment thread generators/typescript/utils/core-utilities/src/core/xml/parse.ts Outdated
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Docs Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-09-18T04:06:24Z).

Fixture main PR Delta
docs 266.3s (n=5) 198.7s (35 versions) -67.6s (-25.4%)

Docs generation runs fern generate --docs --preview end-to-end against the benchmark fixture with 35 API versions (each version: markdown processing + OpenAPI-to-IR + FDR upload).
Delta is computed against the nightly baseline on main.
Baseline from nightly run(s) on main (latest: 2026-09-18T04:06:24Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-09-18 15:52 UTC

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-09-18T04:06:24Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 82s (n=5) 113s (n=5) 64s -18s (-22.0%)
go-sdk square 124s (n=5) 295s (n=5) 93s -31s (-25.0%)
java-sdk square 187s (n=5) 268s (n=5) 145s -42s (-22.5%)
php-sdk square 65s (n=5) N/A 47s -18s (-27.7%)
python-sdk square 142s (n=5) 257s (n=5) 101s -41s (-28.9%)
ruby-sdk-v2 square 93s (n=5) 112s (n=5) 84s -9s (-9.7%)
rust-sdk square 181s (n=5) 204s (n=5) 153s -28s (-15.5%)
swift-sdk square 58s (n=5) 435s (n=5) 57s -1s (-1.7%)
ts-sdk square 136s (n=5) 139s (n=5) 114s -22s (-16.2%)

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-09-18T04:06:24Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-09-18 15:52 UTC

…ed XML properties, preserve unknown children inside wrappers

- getXmlValueShape no longer marks nullable<T> as optional; required nullable
  properties stay required keys and parse to null when absent
- Set-valued scalar/element properties serialize as lists and parse back to Set
- xmlUnknownChildren is wrapper-aware so undeclared children (and attributes)
  inside a wrapped list round-trip without duplicating known items
- xmlBuildAll preserves null/undefined; noOptionalProperties snapshot coverage
- add ir-to-jsonschema snapshots for the ts-xml-twiml fixture

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant