Skip to content

Document XSD differences vs admin PR 2258#5

Draft
Copilot wants to merge 1 commit intoadd-xml-xsd-schemafrom
copilot/evaluate-xsd-differences
Draft

Document XSD differences vs admin PR 2258#5
Copilot wants to merge 1 commit intoadd-xml-xsd-schemafrom
copilot/evaluate-xsd-differences

Conversation

Copy link

Copilot AI commented Jan 17, 2026

Summarizes the comparison between this repository’s booking XSD and the admin PR 2258 booking XSD, highlighting structural matches and differences in cardinality, nullability, typing, and enumerations (e.g., transfer_rate union, status enum, location type enum, CDATA handling).

Original prompt

Evaluate the differences between the XSD present in this branch vs the one being proposed in the repo suntransfers/admin.suntransfers.com via the PR 2258.

Report what matches and what doesn't.

Custom agent used: Principal Software Engineer
Senior engineering agent for software development tasks, architecture work, refactoring, and cross-repository discovery across this GitHub organization


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Evaluate XSD differences between branches Document XSD differences vs admin PR 2258 Jan 17, 2026
Copilot AI requested a review from that-lucas January 17, 2026 01:46
@that-lucas
Copy link
Member

@copilot, the current one from this repo was generated by reflection from a candidate C# class mapping that XML, but the one from admin repo PR was generated based on the actual code generating the XML, so it's more trustworthy than this one, so let’s trust that one.

How would a C# class have to look like so that a XSD generated schema of it would match the correct one (from admin)? Write the class here based on that XSD.

Copy link
Author

Copilot AI commented Jan 17, 2026

@copilot, the current one from this repo was generated by reflection from a candidate C# class mapping that XML, but the one from admin repo PR was generated based on the actual code generating the XM...

Here’s a C# POCO set that, when XSD is generated (e.g., with xsd.exe/svcutil), will match the admin PR 2258 schema:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;

[XmlRoot("booking")]
public class Booking
{
    [XmlElement("reference")] public string Reference { get; set; }
    [XmlElement("agency_name", IsNullable = true)] public string AgencyName { get; set; }
    [XmlElement("agency_reference", IsNullable = true)] public string AgencyReference { get; set; }
    [XmlElement("customer")] public Customer Customer { get; set; }
    [XmlElement("lead_passenger")] public LeadPassenger LeadPassenger { get; set; }
    [XmlElement("transfers")] public Transfers Transfers { get; set; }
}

public class Customer
{
    [XmlElement("name")] public string Name { get; set; }
    [XmlElement("surname")] public string Surname { get; set; }
}

public class LeadPassenger
{
    [XmlElement("name")] public string Name { get; set; }
    [XmlElement("surname")] public string Surname { get; set; }
    [XmlElement("email")] public string Email { get; set; }
    [XmlElement("mobile")] public string Mobile { get; set; }
}

public class Transfers
{
    [XmlElement("transfer")] public List<Transfer> Items { get; set; } = new();
}

public class Transfer
{
    [XmlElement("transfer_document")] public string TransferDocument { get; set; }

    // Allows empty string or decimal text; keep as string to align with emptyOrDecimal union.
    [XmlElement("transfer_rate", IsNullable = true)] public string TransferRate { get; set; }

    [XmlElement("vehicle")] public Vehicle Vehicle { get; set; }
    [XmlElement("passengers")] public Passengers Passengers { get; set; }
    [XmlElement("origin")] public Location Origin { get; set; }
    [XmlElement("destination")] public Location Destination { get; set; }

    [XmlElement("extras")]
    public Extras Extras { get; set; }

    [XmlElement("status")]
    public TransferStatus? Status { get; set; }

    [XmlElement("creation_date")]
    public string CreationDate { get; set; }
}

public enum TransferStatus
{
    CREATED,
    MODIFIED,
    CANCELLED
}

public class Vehicle
{
    [XmlElement("code", IsNullable = true)] public string Code { get; set; }
    [XmlElement("title", IsNullable = true)] public string Title { get; set; }
    [XmlElement("max_passengers", IsNullable = true)] public string MaxPassengers { get; set; }
    [XmlElement("type", IsNullable = true)] public string Type { get; set; }
}

public class Passengers
{
    [XmlElement("total_passengers")] public string TotalPassengers { get; set; }
    [XmlElement("number_children")] public string NumberChildren { get; set; }
    [XmlElement("number_babies")] public string NumberBabies { get; set; }
    [XmlElement("child_ages", IsNullable = true)] public string ChildAges { get; set; }
}

public class Location
{
    [XmlAttribute("type")]
    public LocationType Type { get; set; }

    [XmlElement("id")] public string Id { get; set; }
    [XmlElement("name")] public string Name { get; set; }

    [XmlElement("pickup_time", IsNullable = true)] public string PickupTime { get; set; }

    [XmlElement("flight")]
    public Flight Flight { get; set; }

    [XmlElement("accommodation")]
    public Accommodation Accommodation { get; set; }
}

public enum LocationType
{
    [XmlEnum("airport")] Airport,
    [XmlEnum("city")] City
}

public class Flight
{
    [XmlElement("airline", IsNullable = true)] public string Airline { get; set; }
    [XmlElement("flight_number", IsNullable = true)] public string FlightNumber { get; set; }
    [XmlElement("flight_number_airline", IsNullable = true)] public string FlightNumberAirline { get; set; }
    [XmlElement("flight_number_digits", IsNullable = true)] public string FlightNumberDigits { get; set; }
    [XmlElement("date", IsNullable = true)] public string Date { get; set; }
    [XmlElement("origin_airport")]
    public string OriginAirport { get; set; }
}

public class Accommodation
{
    [XmlElement("name", IsNullable = true)] public string Name { get; set; }
    [XmlElement("address", IsNullable = true)] public string Address { get; set; }
    [XmlElement("pickup_name", IsNullable = true)] public string PickupName { get; set; }
    [XmlElement("pickup_address", IsNullable = true)] public string PickupAddress { get; set; }
    [XmlElement("giata_code", IsNullable = true)] public string GiataCode { get; set; }
}

public class Extras
{
    [XmlElement("extra")] public List<Extra> Items { get; set; } = new();
}

public class Extra
{
    [XmlElement("name")] public string Name { get; set; }
    [XmlElement("quantity")] public string Quantity { get; set; }
    [XmlElement("rate")] public decimal Rate { get; set; }
}

Notes:

  • Using string for the numeric-looking fields mirrors the admin XSD’s string types (and lets transfer_rate accept empty or decimal). If you keep them int/decimal, the generated XSD will differ.
  • IsNullable=true plus nullable reference types (or optional/nullable value types) align with the nillable and minOccurs="0" semantics in the admin schema.
  • Location.type is an enum constrained to airport|city; TransferStatus constrains the status values.
  • Lists (Transfers, Extras) yield maxOccurs="unbounded" on their elements.

If you want origin_airport to be optional like the admin schema’s minOccurs="0", add IsNullable=true and make it nullable (e.g., public string OriginAirport { get; set; } = null;).

@that-lucas
Copy link
Member

@copilot

  1. Using nullable reference types enabled, those IsNullable = true fields would be nullable, correct? (e.g. [XmlElement("airline", IsNullable = true)] public string? Airline { get; set; }

  2. Can we use XmlEnum also in TransferStatus to ensure standard C# naming in those items? (Created, Modified, Cancelled)

  3. The idea of having [XmlElement("transfers")] public Transfers Transfers { get; set; } and then a Transfers class with an Item property looks a bit non-standard in C#. Could that be done in the traditional way of having an IReadOnlyList<Transfer> Transfers and have the XML attributes map that properly?

I want to achieve a more naturally-looking C# class but having XML attributes completely mapping it 1:1 to Admin's XSD.

Optimally we should have this done without having backing fields/copy of properties for the sake of aligning the schema.

Copy link
Author

Copilot AI commented Jan 17, 2026

@copilot

  1. Using nullable reference types enabled, those IsNullable = true fields would be nullable, correct? (e.g. `[XmlElement("airline", IsNullable = true)] public string? Airline { get; set;...
  1. Yes. With nullable reference types enabled, string? + IsNullable = true will emit nillable elements. Example:
[XmlElement("airline", IsNullable = true)]
public string? Airline { get; set; }
  1. You can apply XmlEnum to keep C# casing while emitting the lowercase values:
public enum TransferStatus
{
    [XmlEnum("CREATED")] Created,
    [XmlEnum("MODIFIED")] Modified,
    [XmlEnum("CANCELLED")] Cancelled
}
  1. You can expose a natural list property and still get the same XML. Two common patterns:
  • Direct list with XmlElement:
public class Booking
{
    [XmlArray("transfers")]
    [XmlArrayItem("transfer")]
    public List<Transfer> Transfers { get; set; } = new();
}

This produces <transfers><transfer>…</transfer>…</transfers>.

  • If you prefer IReadOnlyList, you can keep a private setter/backing list for serialization:
public class Booking
{
    [XmlArray("transfers")]
    [XmlArrayItem("transfer")]
    public List<Transfer> TransfersInternal { get; set; } = new();

    [XmlIgnore]
    public IReadOnlyList<Transfer> Transfers => TransfersInternal;
}

This keeps consumers on IReadOnlyList without needing duplicate data; the serializer uses the List property, and the read-only property simply wraps it.

@that-lucas
Copy link
Member

@copilot I like the simpler direction of a List property with no extra magic.

Now let’s try to get that string/numeric union sorted in a proper C# way.

What do you suggest so we have a nullable numeric type property with get/set and a string get-only property that sources from the numeric property?

Copy link
Author

Copilot AI commented Jan 17, 2026

@copilot I like the simpler direction of a List property with no extra magic.

Now let’s try to get that string/numeric union sorted in a proper C# way....

Use a dual-property pattern: a nullable numeric for code, and a string for XML that mirrors the empty-or-decimal union. Example:

using System.Globalization;

public class Transfer
{
    [XmlIgnore]
    public decimal? TransferRate { get; set; }

    [XmlElement("transfer_rate", IsNullable = true)]
    public string? TransferRateText
    {
        get => TransferRate?.ToString("0.##", CultureInfo.InvariantCulture);
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                TransferRate = null; // empty -> nil/empty
                return;
            }
            if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var d))
            {
                TransferRate = d;
                return;
            }
            throw new InvalidOperationException($"Invalid transfer_rate: '{value}'");
        }
    }
}
  • TransferRate is the natural nullable numeric property for code.
  • TransferRateText is the serialized field that accepts empty or decimal, satisfying the XSD union.
  • Invariant culture avoids locale issues; format can be adjusted if more decimal precision is needed.

@that-lucas
Copy link
Member

@copilot

  1. That looks good, but do I need that setter if I’ll be only serializing it, not deserializing?

  2. Put together the final version of this class in the root of this repo incorporating my comments here with change requested.

Copy link
Author

Copilot AI commented Jan 17, 2026

@copilot

  1. That looks good, but do I need that setter if I’ll be only serializing it, not deserializing?...
  1. If you never deserialize, you can drop the setter on the string proxy; XmlSerializer only needs a getter for serialization. I keep the setter so the class can round-trip and still validate the union, but for write-only scenarios a getter is enough.

  2. Final consolidated model (nullable refs, XmlEnum names, list mapping, and the numeric/string union):

#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Serialization;

[XmlRoot("booking")]
public class Booking
{
    [XmlElement("reference")] public string Reference { get; set; } = default!;
    [XmlElement("agency_name", IsNullable = true)] public string? AgencyName { get; set; }
    [XmlElement("agency_reference", IsNullable = true)] public string? AgencyReference { get; set; }
    [XmlElement("customer")] public Customer Customer { get; set; } = new();
    [XmlElement("lead_passenger")] public LeadPassenger LeadPassenger { get; set; } = new();

    [XmlArray("transfers")]
    [XmlArrayItem("transfer")]
    public List<Transfer> Transfers { get; set; } = new();
}

public class Customer
{
    [XmlElement("name")] public string Name { get; set; } = default!;
    [XmlElement("surname")] public string Surname { get; set; } = default!;
}

public class LeadPassenger
{
    [XmlElement("name")] public string Name { get; set; } = default!;
    [XmlElement("surname")] public string Surname { get; set; } = default!;
    [XmlElement("email")] public string Email { get; set; } = default!;
    [XmlElement("mobile")] public string Mobile { get; set; } = default!;
}

public class Transfer
{
    [XmlElement("transfer_document")] public string TransferDocument { get; set; } = default!;

    [XmlIgnore] public decimal? TransferRate { get; set; }

    [XmlElement("transfer_rate", IsNullable = true)]
    public string? TransferRateText
    {
        get => TransferRate?.ToString(CultureInfo.InvariantCulture);
        set
        {
            if (string.IsNullOrWhiteSpace(value)) { TransferRate = null; return; }
            if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) { TransferRate = d; return; }
            throw new InvalidOperationException($"Invalid transfer_rate: '{value}'");
        }
    }

    [XmlElement("vehicle")] public Vehicle Vehicle { get; set; } = new();
    [XmlElement("passengers")] public Passengers Passengers { get; set; } = new();
    [XmlElement("origin")] public Location Origin { get; set; } = new();
    [XmlElement("destination")] public Location Destination { get; set; } = new();

    [XmlArray("extras")]
    [XmlArrayItem("extra")]
    public List<Extra>? Extras { get; set; }

    [XmlElement("status")] public TransferStatus? Status { get; set; }
    [XmlElement("creation_date")] public string? CreationDate { get; set; }
}

public enum TransferStatus
{
    [XmlEnum("CREATED")] Created,
    [XmlEnum("MODIFIED")] Modified,
    [XmlEnum("CANCELLED")] Cancelled
}

public class Vehicle
{
    [XmlElement("code", IsNullable = true)] public string? Code { get; set; }
    [XmlElement("title", IsNullable = true)] public string? Title { get; set; }
    [XmlElement("max_passengers", IsNullable = true)] public string? MaxPassengers { get; set; }
    [XmlElement("type", IsNullable = true)] public string? Type { get; set; }
}

public class Passengers
{
    [XmlElement("total_passengers")] public string TotalPassengers { get; set; } = default!;
    [XmlElement("number_children")] public string NumberChildren { get; set; } = default!;
    [XmlElement("number_babies")] public string NumberBabies { get; set; } = default!;
    [XmlElement("child_ages", IsNullable = true)] public string? ChildAges { get; set; }
}

public class Location
{
    [XmlAttribute("type")] public LocationType Type { get; set; }
    [XmlElement("id")] public string Id { get; set; } = default!;
    [XmlElement("name")] public string Name { get; set; } = default!;
    [XmlElement("pickup_time", IsNullable = true)] public string? PickupTime { get; set; }
    [XmlElement("flight")] public Flight? Flight { get; set; }
    [XmlElement("accommodation")] public Accommodation? Accommodation { get; set; }
}

public enum LocationType
{
    [XmlEnum("airport")] Airport,
    [XmlEnum("city")] City
}

public class Flight
{
    [XmlElement("airline", IsNullable = true)] public string? Airline { get; set; }
    [XmlElement("flight_number", IsNullable = true)] public string? FlightNumber { get; set; }
    [XmlElement("flight_number_airline", IsNullable = true)] public string? FlightNumberAirline { get; set; }
    [XmlElement("flight_number_digits", IsNullable = true)] public string? FlightNumberDigits { get; set; }
    [XmlElement("date", IsNullable = true)] public string? Date { get; set; }
    [XmlElement("origin_airport", IsNullable = true)] public string? OriginAirport { get; set; }
}

public class Accommodation
{
    [XmlElement("name", IsNullable = true)] public string? Name { get; set; }
    [XmlElement("address", IsNullable = true)] public string? Address { get; set; }
    [XmlElement("pickup_name", IsNullable = true)] public string? PickupName { get; set; }
    [XmlElement("pickup_address", IsNullable = true)] public string? PickupAddress { get; set; }
    [XmlElement("giata_code", IsNullable = true)] public string? GiataCode { get; set; }
}

public class Extra
{
    [XmlElement("name")] public string Name { get; set; } = default!;
    [XmlElement("quantity")] public string Quantity { get; set; } = default!;
    [XmlElement("rate")] public decimal Rate { get; set; }
}
  • Drop the TransferRateText setter if you truly only serialize; keep it to allow round-tripping and validation on read.

@that-lucas
Copy link
Member

@copilot now compare with the existing XML Booking class in the supplier-hub repo. Imagine we want to replace that class with this one you just wrote, what else would need to be changed there?

Copy link
Author

Copilot AI commented Jan 17, 2026

@copilot now compare with the existing XML Booking class in the supplier-hub repo. Imagine we want to replace that class with this one you just wrote, what else would need to be changed there?

To swap in this model in supplier-hub, you’d need to:

  • Serializer mappings: Ensure any XmlSerializer instantiation (cached serializers, custom attributes, or overrides) points at the new types/namespaces. If namespaces were previously set via XmlSerializerNamespaces, keep them consistent (the admin XSD has no custom namespace, so default is fine).

  • DTO references: Replace usages of the old Booking class with this Booking type everywhere it’s serialized/deserialized (request/response DTOs, message payloads, API contracts). Update type registrations in DI if the model was injected or used in factories/builders.

  • Transfer rate handling: Drop prior string/decimal handling and wire to Transfer.TransferRate (nullable decimal) while keeping TransferRateText for serialization. Any validation or formatting should move to the numeric property to avoid double-handling.

  • Enums: Update any string comparisons to use TransferStatus/LocationType enums (or XmlEnum names). Remove hardcoded strings if present.

  • Collections: Update code that expected wrapper classes (e.g., Transfers.Items) to work directly with List<Transfer>; adjust mappings in AutoMapper or manual mappers accordingly.

  • Nullability: If nullable reference types are enabled, ensure call sites initialize required properties (Reference, Customer, etc.) and handle nullable fields (nillable XML) defensively.

  • Tests/fixtures: Update XML samples and test fixtures to match the new serializer output (especially the transfer_rate empty/decimal behavior and enum values). Re-record golden files if snapshot-based.

  • Validation: If there was custom validation tied to the old model (e.g., [Required], regex, or fluent validators), port it to the new properties/enums.

  • Builders/parsers: Adjust any XML parsing helpers that previously consumed raw strings for transfer_rate or location type; they should now parse via the enum/decimal properties.

@that-lucas
Copy link
Member

@copilot Evaluate my last question by actually investigating the suntransfers/supplier-hub repo.

Copy link
Author

Copilot AI commented Jan 17, 2026

@copilot Evaluate my last question by actually investigating the suntransfers/supplier-hub repo.

I don’t have access to the suntransfers/supplier-hub repository via the available tooling, so I can’t inspect its current Booking class or usage sites directly. If you can grant access or paste the relevant files (Booking model, serializers, and any XML helpers), I’ll map the exact changes needed to swap in the new model.

@that-lucas
Copy link
Member

@copilot In this case, please create a rich summary of our conversation (in MD format) including the final version of the C# class so that I can use as a conversation starter with another agent that has access to it.

This summary must be very complete so we don't loose context. Think of it as a "conversation serialized" so that I can deserialize it with the other agent and follow on without having to go back and discuss again things already resolved here.

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.

2 participants