diff --git a/README.md b/README.md index 70cbdf8..4e27baa 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,31 @@ namespace Example } ``` +## BXML `` example + +```csharp +using Bandwidth.Standard.Model.Bxml; +using Bandwidth.Standard.Model.Bxml.Verbs; + +var refer = new Refer() + .WithSipUri("sip:alice@atlanta.example.com") + .WithReferCompleteUrl("https://example.com/handleRefer") + .WithReferCompleteMethod("POST") + .WithTag("handoff-123"); + +var bxml = new Response(refer).ToBXML(); + +When a REFER succeeds, the remote SIP endpoint redirects away from Bandwidth and the call is terminated. Do not expect post-success BXML execution on that call leg. + +## `referComplete` failure recovery example + +```csharp +// Example callback handler pseudocode +if (callback.ReferCallStatus == ReferCallStatusEnum.Failure) +{ + // Recover only on failure (e.g. fallback flow, retry, alternate route) +} + ## Documentation for API Endpoints diff --git a/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs b/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs new file mode 100644 index 0000000..9064e42 --- /dev/null +++ b/src/Bandwidth.Standard.Test/Unit/Model/Bxml/TestRefer.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Xml.Serialization; +using Bandwidth.Standard.Model.Bxml; +using Bandwidth.Standard.Model.Bxml.Verbs; +using Xunit; + +namespace Bandwidth.Standard.Test.Unit.Model.Bxml +{ + public class TestRefer + { + [Fact] + public void ReferRoundTripTest() + { + var expected = " sip:alice@atlanta.example.com "; + + var refer = new Refer() + .WithSipUri("sip:alice@atlanta.example.com") + .WithReferCompleteUrl("https://example.com/handleRefer") + .WithReferCompleteMethod("POST") + .WithTag("refer-tag"); + + var actual = new Response(refer).ToBXML(); + Assert.Equal(expected, actual.Replace("\n", "").Replace("\r", "")); + + const string referOnlyXml = "sip:alice@atlanta.example.com"; + var serializer = new XmlSerializer(typeof(Refer), ""); + Refer deserializedRefer; + using (var reader = new StringReader(referOnlyXml)) + { + deserializedRefer = (Refer)serializer.Deserialize(reader); + } + + Assert.Equal("sip:alice@atlanta.example.com", deserializedRefer.SipUriElement.Uri); + var roundTrip = new Response(deserializedRefer).ToBXML(); + Assert.Equal(expected, roundTrip.Replace("\n", "").Replace("\r", "")); + } + + [Fact] + public void ReferSipUriMustStartWithSipScheme() + { + var refer = new Refer(); + Assert.Throws(() => refer.WithSipUri("tel:+15551234567")); + } + + [Fact] + public void ReferInvalidMethodThrows() + { + var refer = new Refer(); + Assert.Throws(() => refer.WithReferCompleteMethod("DELETE")); + } + + [Fact] + public void ReferWithSipUriObjectOverload() + { + var sipUri = new Refer.SipUri { Uri = "sip:alice@atlanta.example.com" }; + var refer = new Refer().WithSipUri(sipUri); + Assert.Equal("sip:alice@atlanta.example.com", refer.SipUriElement.Uri); + } + + [Fact] + public void ReferMinimalOnlySipUri() + { + var refer = new Refer().WithSipUri("sip:bob@biloxi.example.com"); + var bxml = new Response(refer).ToBXML(); + Assert.Contains("sip:bob@biloxi.example.com", bxml); + Assert.Contains("referCompleteMethod=\"POST\"", bxml); + } + } +} \ No newline at end of file diff --git a/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs b/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs new file mode 100644 index 0000000..a712af8 --- /dev/null +++ b/src/Bandwidth.Standard/Model/Bxml/Verbs/Refer.cs @@ -0,0 +1,128 @@ +using Bandwidth.Standard.Model.Bxml; +using System; +using System.Xml.Serialization; + +namespace Bandwidth.Standard.Model.Bxml.Verbs +{ + /// + /// The Refer verb is used to hand off a call to a SIP endpoint. + /// + /// + public class Refer : IVerb + { + private string referCompleteMethod; + + /// + /// URL to receive the refer complete callback. + /// + [XmlAttribute("referCompleteUrl")] + public string ReferCompleteUrl { get; set; } + + /// + /// HTTP method to send the refer complete callback. GET or POST. Default value is POST. + /// + [XmlAttribute("referCompleteMethod")] + public string ReferCompleteMethod + { + get { return referCompleteMethod; } + set + { + if (value != null && value != "GET" && value != "POST") + { + throw new ArgumentException("ReferCompleteMethod must be either 'GET' or 'POST'."); + } + referCompleteMethod = value; + } + } + + /// + /// Optional custom string to include in callbacks. + /// + [XmlAttribute("tag")] + public string Tag { get; set; } + + /// + /// SIP URI destination for the REFER. + /// + [XmlElement("SipUri")] + public SipUri SipUriElement { get; set; } + + /// + /// Initializes a new instance of the Refer class. + /// + public Refer() + { + ReferCompleteMethod = "POST"; + } + + /// + /// Sets the SIP URI destination from a string. + /// + public Refer WithSipUri(string sipUri) + { + SipUriElement = new SipUri { Uri = sipUri }; + return this; + } + + /// + /// Sets the SIP URI destination from a SipUri object. + /// + public Refer WithSipUri(SipUri sipUri) + { + SipUriElement = sipUri; + return this; + } + + /// + /// Sets referCompleteUrl. + /// + public Refer WithReferCompleteUrl(string referCompleteUrl) + { + ReferCompleteUrl = referCompleteUrl; + return this; + } + + /// + /// Sets referCompleteMethod. + /// + public Refer WithReferCompleteMethod(string referCompleteMethod) + { + ReferCompleteMethod = referCompleteMethod; + return this; + } + + /// + /// Sets tag. + /// + public Refer WithTag(string tag) + { + Tag = tag; + return this; + } + + /// + /// BXML tag to represent a SIP URI for the refer verb. + /// + public class SipUri : IVerb + { + private string _uri; + + /// + /// SIP URI to refer the call to (must start with sip:). + /// + [XmlText] + public string Uri + { + get { return _uri; } + set + { + if (value != null && !value.StartsWith("sip:", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("SipUri must start with 'sip:'."); + } + _uri = value; + } + } + } + } +} \ No newline at end of file