Skip to content

feat(csharp): generate ToXml()/FromXml()/builders for xml-encoded object types (TwiML) - #17787

Open
devin-ai-integration[bot] wants to merge 5 commits into
mainfrom
devin/1789749494-csharp-xml-twiml
Open

devin-ai-integration[bot] wants to merge 5 commits into
mainfrom
devin/1789749494-csharp-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 (none)

C# counterpart of #17734 (Python), #17755 (Java) and #17785 (TypeScript): object types carrying IR encoding.xml metadata (from #17732, e.g. Twilio TwiML) now generate XML serialization, parsing and fluent child builders. Types without XML metadata are untouched; the XML runtime files are only emitted when the IR contains at least one xml-encoded type.

Generated shape (from the csharp-xml-twiml fixture):

public record Dial : IJsonOnDeserialized, IXmlNode
{
    // ...existing JSON properties...
    [JsonIgnore] public Dictionary<string, string> AdditionalAttributes { get; set; } = new();
    [JsonIgnore] public List<XmlElement> AdditionalChildren { get; set; } = new();

    public XElement ToXElement();            // <tw:Dial xmlns:tw="https://www.twilio.com/twiml" ...>
    public string ToXml();
    public static Dial FromXml(string xml);   // validates root name, rejects DTDs
    public static Dial FromXElement(XElement element);

    public Dial AddNumber(Number number);    // `Add` prefix only where the tag collides with a property (Dial.Number text)
    public Dial Client(Client client);
    public Dial AddChild(XmlElement child);  // escape hatch for arbitrary elements
}

Design notes (hard to see from the diff):

  • Runtime is System.Xml / System.Xml.Linq only, no new dependencies. XmlUtils is internal (core); IXmlNode and XmlElement are public (public core).
  • Unknown attributes/children are preserved in AdditionalAttributes / AdditionalChildren and re-emitted by ToXElement() (typed children first, unknown after); unknown children inside a wrapped list (<Numbers><Extension/></Numbers>) are kept without duplicating known items.
  • Child unions dispatch on element local name in both modes: OneOf<Say, Dial, ...> (default) and generated undiscriminated-union classes (use-undiscriminated-unions: true) — the fixture runs both.
  • FromXElement uses an object initializer, so the 256-attribute Wide type parses without a giant constructor.
  • Enum wire values, Set/List attributes with listSeparator, namespaces/prefixes and xml:-prefixed attributes round-trip. Fixed XmlElement.ToXElement() to route attributes through XmlUtils.SetAttribute so xml:lang doesn't throw.
  • IR pin bumped 67.21.067.25.0 in the four C# packages.

Changes Made

  • generators/csharp/base/src/asIs/{IXmlNode,XmlElement,XmlUtils}.Template.cs + test/Xml/XmlElementTests.Template.cs (runtime + shipped unit tests), registered in AsIs.ts; emitted conditionally via GeneratorContext.hasXmlTypes() in model + sdk contexts
  • generators/csharp/model/src/object/XmlObjectGenerator.ts: generates the interface impl, ToXElement/ToXml/FromXml/FromXElement, child-union parsers and builder methods; wired from ObjectGenerator only when typeDeclaration.encoding.xml != null
  • codegen: System.Xml.Linq.XElement extern + XmlUtils/XmlElement/IXmlNode references
  • New csharp-xml-twiml test definition (Response/Say/Dial/Number/Client/Pause/Hangup/Wide/PlainObject: attrs, text, tw: namespace, wrapped Numbers, space-separated lists, empty elements, 256-attr type, non-xml type) with two seed configs; seed output + ir-to-jsonschema snapshots committed
  • Changelog: generators/csharp/sdk/changes/unreleased/feat-xml-encoded-types.yml
  • Updated README.md generator (if applicable)

Testing

  • Unit tests added/updated — XmlElementTests ships into every generated SDK that has xml types (rendering/parsing, namespaces, declarations/comments, malformed XML, DTD rejection, wrong root, scalar/list parsing, wrapper unknown-child preservation)
  • Manual testing completed
    • pnpm seed test --generator csharp-sdk --fixture csharp-xml-twiml --skip-scripts → 2/2 passed
    • dotnet test on generated no-custom-config output with a temporary TwiML round-trip suite (attrs/text/escaping, tw: namespace, wrapped lists, list attrs, enums, unknown attrs+children incl. xml:lang, Wide, FromXml(ToXml(x)) equality, error cases): Passed: 186, Failed: 0; use-undiscriminated-unions output: Passed: 186, Failed: 0 (round-trip suite adapted to ResponseChildrenItem)
    • Generated project builds for net462;net8.0;net9.0;netstandard2.0
    • pnpm seed test --generator csharp-sdk --fixture exhaustive → 8/8, zero xml-related output diff (no regression for non-xml types)
    • pnpm turbo run compile --filter @fern-api/fern-csharp-sdk --filter @fern-api/fern-csharp-model → 16/16 tasks

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

…ect 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 3 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread generators/csharp/base/src/asIs/XmlElement.Template.cs
Comment thread generators/csharp/base/src/asIs/XmlElement.Template.cs
Comment thread generators/csharp/model/src/object/XmlObjectGenerator.ts
…eject non-xml object children at generation

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@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) 57s -25s (-30.5%)
go-sdk square 124s (n=5) 295s (n=5) 110s -14s (-11.3%)
java-sdk square 187s (n=5) 268s (n=5) 214s +27s (+14.4%)
php-sdk square 65s (n=5) N/A 50s -15s (-23.1%)
python-sdk square 142s (n=5) 257s (n=5) 170s +28s (+19.7%)
ruby-sdk-v2 square 93s (n=5) 112s (n=5) 75s -18s (-19.4%)
rust-sdk square 181s (n=5) 204s (n=5) 187s +6s (+3.3%)
swift-sdk square 58s (n=5) 435s (n=5) 49s -9s (-15.5%)
ts-sdk square 136s (n=5) 139s (n=5) 111s -25s (-18.4%)

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 19:22 UTC

@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) 253.4s (35 versions) -12.9s (-4.8%)

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 19:22 UTC

cadesark and others added 2 commits September 18, 2026 18:33
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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