-
Notifications
You must be signed in to change notification settings - Fork 16
Enable nullable globally and fix issues #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6b3ad42
d67f11c
c54af88
acddd18
7e13391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<Project> | ||
|
||
<PropertyGroup> | ||
<Nullable>enable</Nullable> | ||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
<langversion>10.0</langversion> | ||
</PropertyGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,57 @@ | ||
using Ipfs.CoreApi; | ||
using Newtonsoft.Json.Linq; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Newtonsoft.Json.Linq; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Ipfs.Http | ||
{ | ||
class BitswapApi : IBitswapApi | ||
{ | ||
IpfsClient ipfs; | ||
|
||
internal BitswapApi(IpfsClient ipfs) | ||
{ | ||
this.ipfs = ipfs; | ||
namespace Ipfs.Http | ||
{ | ||
class BitswapApi : IBitswapApi | ||
{ | ||
private readonly IpfsClient ipfs; | ||
internal BitswapApi(IpfsClient ipfs) | ||
{ | ||
this.ipfs = ipfs; | ||
} | ||
|
||
public Task<IDataBlock> GetAsync(Cid id, CancellationToken cancel = default(CancellationToken)) | ||
public Task<IDataBlock> GetAsync(Cid id, CancellationToken cancel = default) | ||
{ | ||
return ipfs.Block.GetAsync(id, cancel); | ||
} | ||
|
||
public async Task<IEnumerable<Cid>> WantsAsync(MultiHash peer = null, CancellationToken cancel = default(CancellationToken)) | ||
public async Task<IEnumerable<Cid>> WantsAsync(MultiHash? peer = null, CancellationToken cancel = default) | ||
{ | ||
var json = await ipfs.DoCommandAsync("bitswap/wantlist", cancel, peer?.ToString()); | ||
var keys = (JArray)(JObject.Parse(json)["Keys"]); | ||
// https://github.com/ipfs/go-ipfs/issues/5077 | ||
return keys | ||
var keys = (JArray?)(JObject.Parse(json)["Keys"]); | ||
// https://github.com/ipfs/go-ipfs/issues/5077 | ||
return keys | ||
.Select(k => | ||
{ | ||
if (k.Type == JTokenType.String) | ||
return Cid.Decode(k.ToString()); | ||
return Cid.Decode(k.ToString()); | ||
var obj = (JObject)k; | ||
return Cid.Decode(obj["/"].ToString()); | ||
}); | ||
return Cid.Decode(obj["/"]!.ToString()); | ||
}); | ||
} | ||
|
||
public async Task UnwantAsync(Cid id, CancellationToken cancel = default(CancellationToken)) | ||
public async Task UnwantAsync(Cid id, CancellationToken cancel = default) | ||
{ | ||
await ipfs.DoCommandAsync("bitswap/unwant", cancel, id); | ||
} | ||
|
||
public async Task<BitswapLedger> LedgerAsync(Peer peer, CancellationToken cancel = default(CancellationToken)) | ||
public async Task<BitswapLedger> LedgerAsync(Peer peer, CancellationToken cancel = default) | ||
{ | ||
var json = await ipfs.DoCommandAsync("bitswap/ledger", cancel, peer.Id.ToString()); | ||
var json = await ipfs.DoCommandAsync("bitswap/ledger", cancel, peer.Id?.ToString()); | ||
var o = JObject.Parse(json); | ||
return new BitswapLedger | ||
{ | ||
Peer = (string)o["Peer"], | ||
DataReceived = (ulong)o["Sent"], | ||
DataSent = (ulong)o["Recv"], | ||
BlocksExchanged = (ulong)o["Exchanged"] | ||
Peer = (string)o["Peer"]!, | ||
DataReceived = (ulong?)o["Sent"] ?? 0, | ||
DataSent = (ulong?)o["Recv"] ?? 0, | ||
BlocksExchanged = (ulong?)o["Exchanged"] ?? 0, | ||
}; | ||
} | ||
} | ||
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,113 +1,111 @@ | ||||||||
using Ipfs.CoreApi; | ||||||||
using Newtonsoft.Json.Linq; | ||||||||
using System.Collections.Generic; | ||||||||
using Newtonsoft.Json.Linq; | ||||||||
using System.Collections.Generic; | ||||||||
using System.IO; | ||||||||
using System.Net.Http; | ||||||||
using System.Threading; | ||||||||
using System.Threading.Tasks; | ||||||||
using System.Net.Http; | ||||||||
using System.Threading; | ||||||||
using System.Threading.Tasks; | ||||||||
|
||||||||
namespace Ipfs.Http | ||||||||
{ | ||||||||
class BlockApi : IBlockApi | ||||||||
{ | ||||||||
IpfsClient ipfs; | ||||||||
|
||||||||
internal BlockApi(IpfsClient ipfs) | ||||||||
{ | ||||||||
this.ipfs = ipfs; | ||||||||
} | ||||||||
|
||||||||
public async Task<IDataBlock> GetAsync(Cid id, CancellationToken cancel = default(CancellationToken)) | ||||||||
{ | ||||||||
var data = await ipfs.DownloadBytesAsync("block/get", cancel, id); | ||||||||
return new Block | ||||||||
{ | ||||||||
DataBytes = data, | ||||||||
Id = id | ||||||||
}; | ||||||||
} | ||||||||
|
||||||||
public async Task<Cid> PutAsync( | ||||||||
namespace Ipfs.Http | ||||||||
{ | ||||||||
class BlockApi : IBlockApi | ||||||||
{ | ||||||||
private readonly IpfsClient ipfs; | ||||||||
internal BlockApi(IpfsClient ipfs) | ||||||||
{ | ||||||||
this.ipfs = ipfs; | ||||||||
} | ||||||||
public async Task<IDataBlock> GetAsync(Cid id, CancellationToken cancel = default) | ||||||||
{ | ||||||||
var data = await ipfs.DownloadBytesAsync("block/get", cancel, id); | ||||||||
return new Block | ||||||||
{ | ||||||||
DataBytes = data, | ||||||||
Id = id | ||||||||
}; | ||||||||
} | ||||||||
public async Task<Cid> PutAsync( | ||||||||
byte[] data, | ||||||||
string contentType = Cid.DefaultContentType, | ||||||||
string multiHash = MultiHash.DefaultAlgorithmName, | ||||||||
string encoding = MultiBase.DefaultAlgorithmName, | ||||||||
bool pin = false, | ||||||||
CancellationToken cancel = default(CancellationToken)) | ||||||||
{ | ||||||||
var options = new List<string>(); | ||||||||
string contentType = Cid.DefaultContentType, | ||||||||
string multiHash = MultiHash.DefaultAlgorithmName, | ||||||||
string encoding = MultiBase.DefaultAlgorithmName, | ||||||||
bool pin = false, | ||||||||
CancellationToken cancel = default) | ||||||||
{ | ||||||||
var options = new List<string>(); | ||||||||
if (multiHash != MultiHash.DefaultAlgorithmName || | ||||||||
contentType != Cid.DefaultContentType || | ||||||||
encoding != MultiBase.DefaultAlgorithmName) | ||||||||
{ | ||||||||
options.Add($"mhtype={multiHash}"); | ||||||||
options.Add($"format={contentType}"); | ||||||||
options.Add($"cid-base={encoding}"); | ||||||||
} | ||||||||
} | ||||||||
var json = await ipfs.UploadAsync("block/put", cancel, data, options.ToArray()); | ||||||||
var info = JObject.Parse(json); | ||||||||
Cid cid = (string)info["Key"]; | ||||||||
|
||||||||
Cid cid = (string)info["Key"]!; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's nothing stopping this suppressed null value from being returned and passed around to other parts of the application. Instead of suppressing and returning null values in these methods, we should throw instead. Early validation makes for clean stack traces and faster debugging. Let's do this for each method in our core implementations (not just this line), to make sure it doesn't return a supressed null value. |
||||||||
if (pin) | ||||||||
{ | ||||||||
await ipfs.Pin.AddAsync(cid, recursive: false, cancel: cancel); | ||||||||
} | ||||||||
|
||||||||
return cid; | ||||||||
} | ||||||||
|
||||||||
public async Task<Cid> PutAsync( | ||||||||
} | ||||||||
return cid; | ||||||||
} | ||||||||
public async Task<Cid> PutAsync( | ||||||||
Stream data, | ||||||||
string contentType = Cid.DefaultContentType, | ||||||||
string multiHash = MultiHash.DefaultAlgorithmName, | ||||||||
string encoding = MultiBase.DefaultAlgorithmName, | ||||||||
bool pin = false, | ||||||||
CancellationToken cancel = default(CancellationToken)) | ||||||||
{ | ||||||||
var options = new List<string>(); | ||||||||
string contentType = Cid.DefaultContentType, | ||||||||
string multiHash = MultiHash.DefaultAlgorithmName, | ||||||||
string encoding = MultiBase.DefaultAlgorithmName, | ||||||||
bool pin = false, | ||||||||
CancellationToken cancel = default) | ||||||||
{ | ||||||||
var options = new List<string>(); | ||||||||
if (multiHash != MultiHash.DefaultAlgorithmName || | ||||||||
contentType != Cid.DefaultContentType || | ||||||||
encoding != MultiBase.DefaultAlgorithmName) | ||||||||
{ | ||||||||
options.Add($"mhtype={multiHash}"); | ||||||||
options.Add($"format={contentType}"); | ||||||||
options.Add($"cid-base={encoding}"); | ||||||||
} | ||||||||
var json = await ipfs.UploadAsync("block/put", cancel, data, null, options.ToArray()); | ||||||||
} | ||||||||
var json = await ipfs.UploadAsync("block/put", cancel, data, name: null, options.ToArray()); | ||||||||
var info = JObject.Parse(json); | ||||||||
Cid cid = (string)info["Key"]; | ||||||||
|
||||||||
Cid cid = (string)info["Key"]!; | ||||||||
if (pin) | ||||||||
{ | ||||||||
await ipfs.Pin.AddAsync(cid, recursive: false, cancel: cancel); | ||||||||
} | ||||||||
|
||||||||
return cid; | ||||||||
} | ||||||||
|
||||||||
public async Task<IDataBlock> StatAsync(Cid id, CancellationToken cancel = default(CancellationToken)) | ||||||||
{ | ||||||||
var json = await ipfs.DoCommandAsync("block/stat", cancel, id); | ||||||||
} | ||||||||
return cid; | ||||||||
} | ||||||||
public async Task<IDataBlock> StatAsync(Cid id, CancellationToken cancel = default) | ||||||||
{ | ||||||||
var json = await ipfs.DoCommandAsync("block/stat", cancel, id); | ||||||||
var info = JObject.Parse(json); | ||||||||
return new Block | ||||||||
{ | ||||||||
Size = (long)info["Size"], | ||||||||
Id = (string)info["Key"] | ||||||||
}; | ||||||||
} | ||||||||
|
||||||||
public async Task<Cid> RemoveAsync(Cid id, bool ignoreNonexistent = false, CancellationToken cancel = default(CancellationToken)) | ||||||||
{ | ||||||||
var json = await ipfs.DoCommandAsync("block/rm", cancel, id, "force=" + ignoreNonexistent.ToString().ToLowerInvariant()); | ||||||||
if (json.Length == 0) | ||||||||
return null; | ||||||||
var result = JObject.Parse(json); | ||||||||
var error = (string)result["Error"]; | ||||||||
if (error != null) | ||||||||
throw new HttpRequestException(error); | ||||||||
return (Cid)(string)result["Hash"]; | ||||||||
return new Block | ||||||||
{ | ||||||||
Size = (long?)info["Size"] ?? 0, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary fallback value, the API can return null for size.
Suggested change
|
||||||||
Id = (string)info["Key"]! | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use parameter validation here instead of null suppression. |
||||||||
}; | ||||||||
} | ||||||||
|
||||||||
} | ||||||||
|
||||||||
} | ||||||||
public async Task<Cid?> RemoveAsync(Cid id, bool ignoreNonexistent = false, CancellationToken cancel = default) | ||||||||
{ | ||||||||
var json = await ipfs.DoCommandAsync("block/rm", cancel, id, "force=" + ignoreNonexistent.ToString().ToLowerInvariant()); | ||||||||
if (json.Length == 0) | ||||||||
return null; | ||||||||
var result = JObject.Parse(json); | ||||||||
var error = (string?)result["Error"]; | ||||||||
if (error is not null) | ||||||||
throw new HttpRequestException(error); | ||||||||
return (Cid)(string)result["Hash"]!; | ||||||||
erikmav marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
} | ||||||||
} | ||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the API returns a null value for these, we should bubble that up to the consuming application. Let's remove the fallback null coalescence values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes will require a new wave of nullable updates in IpfsCore, as the declarations of the data classes there are not nullable after the recent 0.0.5 updates. Will send a new PR from there.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, why does that require changes in
IpfsCore
? These are being null suppressed, I assumed the json models were already nullable. That's how it should be, as the data could go missing from the API surface if you use a new version of Kubo with breaking changes, or if JSON doesn't deserialize correctly for some reason.For the models exposed to the consumer of
net-ipfs-http-client
, if the official specification says it's nullable or not, then the models inIpfsCore
should reflect that. It's the underlying API model properties used for deserialization that need to be nullable. This is already the case, because we're manually grabbing fields using Newtonsoft (e.g.o["Peer"]
is nullable).We just need to do null check / throw here. Simple validation, so that suppressed null values aren't returned and passed around to other parts of the application.