33using System . IO ;
44using System . Linq ;
55using System . Net . Http ;
6+ using System . Net . Http . Headers ;
7+ using System . Net . Mime ;
68using System . Text ;
79using System . Threading ;
810using System . Threading . Tasks ;
@@ -110,7 +112,7 @@ public interface IPinningEndpoint
110112 /// <summary>
111113 /// This endpoint allows the sender to add and pin any file, or directory, to Pinata's IPFS nodes.
112114 /// </summary>
113- Task < IFlurlResponse > PinFileToIPFSAsync ( Action < CapturedMultipartContent > callback , PinataMetadata pinataMetadata = null , PinataOptions pinataOptions = null , CancellationToken cancellationToken = default ) ;
115+ Task < PinFileToIpfsResponse > PinFileToIPFSAsync ( Action < CapturedMultipartContent > payloadBuilder , PinataMetadata pinataMetadata = null , PinataOptions pinataOptions = null , CancellationToken cancellationToken = default ) ;
114116 }
115117
116118 public partial class PinataClient : IPinningEndpoint
@@ -187,14 +189,32 @@ public Task<UserPinPolicyResponse> UserPinPolicyAsync(PinPolicy newPinPolicy, bo
187189 . ReceiveJson < UserPinPolicyResponse > ( ) ;
188190 }
189191
190- public Task < IFlurlResponse > PinFileToIPFSAsync ( Action < CapturedMultipartContent > callback , PinataMetadata pinataMetadata = null , PinataOptions pinataOptions = null , CancellationToken cancellationToken = default )
192+ public Task < PinFileToIpfsResponse > PinFileToIPFSAsync ( Action < CapturedMultipartContent > payloadBuilder , PinataMetadata pinataMetadata = null , PinataOptions pinataOptions = null , CancellationToken cancellationToken = default )
191193 {
194+ static void AssertAllHttpContentHasFileName ( CapturedMultipartContent content )
195+ {
196+ foreach ( var part in content )
197+ {
198+ var name = part ? . Headers ? . ContentDisposition ? . Name ;
199+ var fileName = part ? . Headers ? . ContentDisposition ? . FileName ;
200+ if ( string . IsNullOrWhiteSpace ( name ) || string . IsNullOrWhiteSpace ( fileName ) )
201+ {
202+ throw new HttpRequestException ( "The 'httpContent.Headers.ContentDisposition.(Name|FileName)' is null or whitespace. " +
203+ "All multi-part upload content must contain a 'Name' and 'FileName' content disposition header value. " +
204+ "Try using the httpContent.AsPinataFile('name.txt') extension method to set the required fields on the HttpContent you are adding." ) ;
205+ }
206+ }
207+ }
208+
192209 return this . PinningEndpoint
193210 . WithClient ( this )
194211 . AppendPathSegment ( "pinFileToIPFS" )
195212 . PostMultipartAsync ( multiPart =>
196213 {
197- callback ( multiPart ) ;
214+ payloadBuilder ( multiPart ) ;
215+
216+ AssertAllHttpContentHasFileName ( multiPart ) ;
217+
198218 if ( pinataMetadata is not null )
199219 {
200220 multiPart . AddJson ( "pinataMetadata" , pinataMetadata ) ;
@@ -203,7 +223,22 @@ public Task<IFlurlResponse> PinFileToIPFSAsync(Action<CapturedMultipartContent>
203223 {
204224 multiPart . AddJson ( "pinataOptions" , pinataOptions ) ;
205225 }
206- } , cancellationToken ) ;
226+ } , cancellationToken )
227+ . ReceiveJson < PinFileToIpfsResponse > ( ) ;
228+ }
229+ }
230+
231+ public static class ExtensionsForHttpContent
232+ {
233+ public static T AsPinataFile < T > ( this T content , string remoteFilePath ) where T : HttpContent
234+ {
235+ var header = new ContentDispositionHeaderValue ( "form-data" )
236+ {
237+ Name = "file" ,
238+ FileName = remoteFilePath
239+ } ;
240+ content . Headers . ContentDisposition = header ;
241+ return content ;
207242 }
208243 }
209244}
0 commit comments