feat(typescript): generate toXml()/fromXml()/builders for xml-encoded object types (TwiML) - #17785
devin-ai-integration[bot] wants to merge 3 commits into
Conversation
… object types (TwiML) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Devin Review found 6 potential issues.
3 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
Docs Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on
Docs generation runs |
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
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 |
…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>
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 OpenAPIxmlobject) 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):class X { fields...; toXml(); toString(); static fromXml(xml); static builder() }+namespace X { interface Fields; class Builder }.Response.Children.Item = Say | Dial | ...); accept aPartial<Fields>or an existing model/builder (xmlBuild/xmlBuildAll). Naming rule B, same as Python/Java: plain camelCase,addprefix on collision with a property/method or when the name is unsafe/reserved.listSeparatoron attributes/text, namespace + prefix, escaping; xml declaration emitted only for document roots (types not used as a child of another xml type —TypeResolver.isXmlChildType).additionalAttributes: Record<string,string>, undeclared children →additionalChildren: XmlElement[]; both re-emitted bytoXml()so documents round-trip. Known limitation shared with Python/Java: mixed-content ordering of unknown children is not preserved (declared children first).XmlParseError;<!DOCTYPE>and other declarations are rejected (no entity expansion beyond the 5 predefined + numeric refs).TypeResolver.isXmlDependentType, via IRreferencedTypeswhich is already transitive); non-xml types in the same API still get schemas (PlainObjectin theserde-layerseed 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 readersxmlAttribute/xmlText/xmlChild(ren)/xmlEnum/...),XmlElement.ts(generic node),builder.ts(XmlBuilder,xmlBuild,xmlBuildAll); +tests/unit/xml/xml.test.tscopied into generated SDKs.utils/commons:core-utilities/Xml.ts(manifestsrc/core/xml/**, copied on demand) andcodegen-utils/xmlTypes.ts(getXmlEncoding,getXmlValueShape,getXmlChildObjectTypes,getXmlChildTypeIds,isXmlDependentType).utils/resolvers/TypeResolver.ts:getTypeDeclarationById,isXmlChildType,isXmlDependentType.model/type-generator: newXmlObjectGenerator.ts;GeneratedObjectTypeImplemits the class + module statements whenxmlis set;TypeGeneratorthreadsxml/isXmlRoot/useBigInt.sdk/generator:TypeContextImplpasses xml metadata + root detection;SdkGenerator.generateTypeSchemasskips xml-dependent types.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-attributeWide, non-xmlPlainObject) registered inseed/ts-sdk/seed.ymlasno-custom-configandserde-layer; seed output committed.generators/typescript/sdk/changes/unreleased/feat-xml-encoded-types.yml.Testing
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 SDKtsc --noEmitclean.fromXml/Builder.fromXml, unknown attrs + children preserved,Wide, malformed / wrong-root / bad scalar / bad enum / DOCTYPE / multiple-root rejection,XmlElementescape hatch.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