11using System . Collections ;
22using System . Collections . Concurrent ;
3+ using System . Diagnostics ;
34using System . Net . Http ;
45using System . Reflection ;
56using System . Text ;
6- using System . Text . RegularExpressions ;
77using System . Web ;
88
99namespace Refit
@@ -14,6 +14,7 @@ class RequestBuilderImplementation<TApi>(RefitSettings? refitSettings = null)
1414
1515 partial class RequestBuilderImplementation : IRequestBuilder
1616 {
17+ private const int StackallocThreshold = 512 ;
1718 static readonly QueryAttribute DefaultQueryAttribute = new ( ) ;
1819 static readonly Uri BaseUri = new ( "http://api" ) ;
1920 readonly Dictionary < string , List < RestMethodInfoInternal > > interfaceHttpMethods ;
@@ -645,8 +646,6 @@ bool paramsContainsCancellationToken
645646 ret . Content = multiPartContent ;
646647 }
647648
648- var urlTarget =
649- ( basePath == "/" ? string . Empty : basePath ) + restMethod . RelativePath ;
650649 var queryParamsToAdd = new List < KeyValuePair < string , string ? > > ( ) ;
651650 var headersToAdd = restMethod . Headers . Count > 0 ?
652651 new Dictionary < string , string ? > ( restMethod . Headers )
@@ -662,14 +661,10 @@ bool paramsContainsCancellationToken
662661 if ( restMethod . ParameterMap . TryGetValue ( i , out var parameterMapValue ) )
663662 {
664663 parameterInfo = parameterMapValue ;
665- if ( parameterInfo . IsObjectPropertyParameter )
664+ if ( ! parameterInfo . IsObjectPropertyParameter )
666665 {
667- urlTarget = AddObjectParametersToUrl ( parameterInfo , param , urlTarget ) ;
668- //don't continue here as we want it to fall through so any parameters on this object not bound here get passed as query parameters
669- }
670- else
671- {
672- urlTarget = AddValueParameterToUrl ( restMethod , parameterMapValue , param , i , urlTarget ) ;
666+ // mark parameter mapped if not an object
667+ // we want objects to fall through so any parameters on this object not bound here get passed as query parameters
673668 isParameterMappedToRequest = true ;
674669 }
675670 }
@@ -758,6 +753,8 @@ bool paramsContainsCancellationToken
758753 // NB: The URI methods in .NET are dumb. Also, we do this
759754 // UriBuilder business so that we preserve any hardcoded query
760755 // parameters as well as add the parameterized ones.
756+ var urlTarget = BuildRelativePath ( basePath , restMethod , paramList ) ;
757+
761758 var uri = new UriBuilder ( new Uri ( BaseUri , urlTarget ) ) ;
762759 ParseExistingQueryString ( uri , queryParamsToAdd ) ;
763760
@@ -778,72 +775,102 @@ bool paramsContainsCancellationToken
778775 } ;
779776 }
780777
781- string AddObjectParametersToUrl ( RestMethodParameterInfo parameterInfo , object param , string urlTarget )
778+ string BuildRelativePath ( string basePath , RestMethodInfoInternal restMethod , object [ ] paramList )
782779 {
783- foreach ( var propertyInfo in parameterInfo . ParameterProperties )
780+ basePath = basePath == "/" ? string . Empty : basePath ;
781+ var pathFragments = restMethod . FragmentPath ;
782+ if ( pathFragments . Count == 0 )
784783 {
785- var propertyObject = propertyInfo . PropertyInfo . GetValue ( param ) ;
786- urlTarget = Regex . Replace (
787- urlTarget ,
788- "{" + propertyInfo . Name + "}" ,
789- Uri . EscapeDataString (
790- settings . UrlParameterFormatter . Format (
791- propertyObject ,
792- propertyInfo . PropertyInfo ,
793- propertyInfo . PropertyInfo . PropertyType
794- ) ?? string . Empty
795- ) ,
796- RegexOptions . IgnoreCase | RegexOptions . CultureInvariant
797- ) ;
784+ return basePath ;
785+ }
786+ if ( string . IsNullOrEmpty ( basePath ) && pathFragments . Count == 1 )
787+ {
788+ Debug . Assert ( pathFragments [ 0 ] . IsConstant ) ;
789+ return pathFragments [ 0 ] . Value ! ;
798790 }
799791
800- return urlTarget ;
792+ #pragma warning disable CA2000
793+ var vsb = new ValueStringBuilder ( stackalloc char [ StackallocThreshold ] ) ;
794+ #pragma warning restore CA2000
795+ vsb . Append ( basePath ) ;
796+
797+ foreach ( var fragment in pathFragments )
798+ {
799+ AppendPathFragmentValue ( ref vsb , restMethod , paramList , fragment ) ;
800+ }
801+
802+ return vsb . ToString ( ) ;
801803 }
802804
803- string AddValueParameterToUrl ( RestMethodInfoInternal restMethod , RestMethodParameterInfo parameterMapValue ,
804- object param , int i , string urlTarget )
805+ void AppendPathFragmentValue ( ref ValueStringBuilder vsb , RestMethodInfoInternal restMethod , object [ ] paramList ,
806+ ParameterFragment fragment )
805807 {
806- string pattern ;
807- string replacement ;
808- if ( parameterMapValue . Type == ParameterType . RoundTripping )
808+ if ( fragment . IsConstant )
809809 {
810- pattern = $@ "{{\*\*{ parameterMapValue . Name } }}";
811- var paramValue = ( string ) param ;
812- replacement = string . Join (
813- "/" ,
814- paramValue
815- . Split ( '/' )
816- . Select (
817- s =>
818- Uri . EscapeDataString (
819- settings . UrlParameterFormatter . Format (
820- s ,
821- restMethod . ParameterInfoArray [ i ] ,
822- restMethod . ParameterInfoArray [ i ] . ParameterType
823- ) ?? string . Empty
824- )
825- )
826- ) ;
810+ vsb . Append ( fragment . Value ! ) ;
811+ return ;
827812 }
828- else
813+
814+ var contains = restMethod . ParameterMap . TryGetValue ( fragment . ArgumentIndex , out var parameterMapValue ) ;
815+ if ( ! contains || parameterMapValue is null )
816+ throw new InvalidOperationException ( $ "{ restMethod . ParameterMap } should contain parameter.") ;
817+
818+ if ( fragment . IsObjectProperty )
829819 {
830- pattern = "{" + parameterMapValue . Name + "}" ;
831- replacement = Uri . EscapeDataString (
832- settings . UrlParameterFormatter . Format (
833- param ,
834- restMethod . ParameterInfoArray [ i ] ,
835- restMethod . ParameterInfoArray [ i ] . ParameterType
836- ) ?? string . Empty
837- ) ;
820+ var param = paramList [ fragment . ArgumentIndex ] ;
821+ var property = parameterMapValue . ParameterProperties [ fragment . PropertyIndex ] ;
822+ var propertyObject = property . PropertyInfo . GetValue ( param ) ;
823+
824+ vsb . Append ( Uri . EscapeDataString ( settings . UrlParameterFormatter . Format (
825+ propertyObject ,
826+ property . PropertyInfo ,
827+ property . PropertyInfo . PropertyType
828+ ) ?? string . Empty ) ) ;
829+ return ;
838830 }
839831
840- urlTarget = Regex . Replace (
841- urlTarget ,
842- pattern ,
843- replacement ,
844- RegexOptions . IgnoreCase | RegexOptions . CultureInvariant
845- ) ;
846- return urlTarget ;
832+ if ( fragment . IsDynamicRoute )
833+ {
834+ var param = paramList [ fragment . ArgumentIndex ] ;
835+
836+ if ( parameterMapValue . Type == ParameterType . Normal )
837+ {
838+ vsb . Append ( Uri . EscapeDataString (
839+ settings . UrlParameterFormatter . Format (
840+ param ,
841+ restMethod . ParameterInfoArray [ fragment . ArgumentIndex ] ,
842+ restMethod . ParameterInfoArray [ fragment . ArgumentIndex ] . ParameterType
843+ ) ?? string . Empty
844+ ) ) ;
845+ return ;
846+ }
847+
848+ // If round tripping, split string up, format each segment and append to vsb.
849+ Debug . Assert ( parameterMapValue . Type == ParameterType . RoundTripping ) ;
850+ var paramValue = ( string ) param ;
851+ var split = paramValue . Split ( '/' ) ;
852+
853+ var firstSection = true ;
854+ foreach ( var section in split )
855+ {
856+ if ( ! firstSection )
857+ vsb . Append ( '/' ) ;
858+
859+ vsb . Append (
860+ Uri . EscapeDataString (
861+ settings . UrlParameterFormatter . Format (
862+ section ,
863+ restMethod . ParameterInfoArray [ fragment . ArgumentIndex ] ,
864+ restMethod . ParameterInfoArray [ fragment . ArgumentIndex ] . ParameterType
865+ ) ?? string . Empty
866+ ) ) ;
867+ firstSection = false ;
868+ }
869+
870+ return ;
871+ }
872+
873+ throw new ArgumentException ( $ "{ nameof ( ParameterFragment ) } is in an invalid form.") ;
847874 }
848875
849876 void AddBodyToRequest ( RestMethodInfoInternal restMethod , object param , HttpRequestMessage ret )
@@ -1168,7 +1195,7 @@ static string CreateQueryString(List<KeyValuePair<string, string?>> queryParamsT
11681195 {
11691196 // Suppress warning as ValueStringBuilder.ToString calls Dispose()
11701197#pragma warning disable CA2000
1171- var vsb = new ValueStringBuilder ( stackalloc char [ 512 ] ) ;
1198+ var vsb = new ValueStringBuilder ( stackalloc char [ StackallocThreshold ] ) ;
11721199#pragma warning restore CA2000
11731200 var firstQuery = true ;
11741201 foreach ( var queryParam in queryParamsToAdd )
0 commit comments