Skip to content

Commit 6590a1d

Browse files
committed
CSHARP-5269: Add ReadGuid and WriteGuid to BSON readers and writers.
1 parent e52bb04 commit 6590a1d

File tree

15 files changed

+613
-68
lines changed

15 files changed

+613
-68
lines changed

src/MongoDB.Bson/IO/BsonBinaryReader.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ public override BsonBinaryData ReadBinaryData()
155155
}
156156

157157
var bytes = _bsonStream.ReadBytes(size);
158+
if ((subType == BsonBinarySubType.UuidStandard || subType == BsonBinarySubType.UuidLegacy) &&
159+
bytes.Length != 16)
160+
{
161+
throw new FormatException($"Length must be 16, not {bytes.Length}, when subType is {subType}.");
162+
}
163+
158164
var binaryData = new BsonBinaryData(bytes, subType);
159165

160166
State = GetNextState();

src/MongoDB.Bson/IO/BsonReader.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,45 @@ public void PushSettings(Action<BsonReaderSettings> configurator)
210210
/// </summary>
211211
public abstract void ReadEndDocument();
212212

213+
/// <inheritdoc/>
214+
public virtual Guid ReadGuid()
215+
{
216+
var binaryData = ReadBinaryData();
217+
var bytes = binaryData.Bytes;
218+
var subType = binaryData.SubType;
219+
220+
if (subType != BsonBinarySubType.UuidStandard)
221+
{
222+
throw new FormatException($"GuidRepresentation is unknown for binary subtype {binaryData.SubType}.");
223+
}
224+
if (bytes.Length != 16)
225+
{
226+
throw new FormatException($"Expected length to be 16, not {bytes.Length}.");
227+
}
228+
229+
return GuidConverter.FromBytes(bytes, GuidRepresentation.Standard);
230+
}
231+
232+
/// <inheritdoc/>
233+
public virtual Guid ReadGuid(GuidRepresentation guidRepresentation)
234+
{
235+
var binaryData = ReadBinaryData();
236+
var bytes = binaryData.Bytes;
237+
var subType = binaryData.SubType;
238+
239+
var expectedSubType = GuidConverter.GetSubType(guidRepresentation);
240+
if (subType != expectedSubType)
241+
{
242+
throw new FormatException($"Expected BsonBinarySubType to be {expectedSubType}, but it is {subType}.");
243+
}
244+
if (bytes.Length != 16)
245+
{
246+
throw new FormatException($"Expected length to be 16, not {bytes.Length}.");
247+
}
248+
249+
return GuidConverter.FromBytes(bytes, guidRepresentation);
250+
}
251+
213252
/// <summary>
214253
/// Reads a BSON Int32 from the reader.
215254
/// </summary>

src/MongoDB.Bson/IO/BsonWriter.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ public virtual void WriteEndArray()
211211
public virtual void WriteEndDocument()
212212
=> ExitSerializationScope();
213213

214+
/// <inheritdoc/>
215+
public virtual void WriteGuid(Guid value) => WriteGuid(value, GuidRepresentation.Standard);
216+
217+
/// <inheritdoc/>
218+
public virtual void WriteGuid(Guid value, GuidRepresentation guidRepresentation)
219+
{
220+
var bytes = GuidConverter.ToBytes(value, guidRepresentation);
221+
var subType = GuidConverter.GetSubType(guidRepresentation);
222+
var binaryData = new BsonBinaryData(bytes, subType);
223+
WriteBinaryData(binaryData);
224+
}
225+
214226
/// <summary>
215227
/// Writes a BSON Int32 to the writer.
216228
/// </summary>

src/MongoDB.Bson/IO/IBsonReader.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,19 @@ public interface IBsonReader : IDisposable
122122
/// </summary>
123123
void ReadEndDocument();
124124

125+
/// <summary>
126+
/// Reads a Guid from the reader.
127+
/// </summary>
128+
/// <returns>A Guid.</returns>
129+
public Guid ReadGuid();
130+
131+
/// <summary>
132+
/// Reads a Guid with the specified representation from the reader.
133+
/// </summary>
134+
/// <param name="guidRepresentation">The Guid representation.</param>
135+
/// <returns>A Guid.</returns>
136+
public Guid ReadGuid(GuidRepresentation guidRepresentation);
137+
125138
/// <summary>
126139
/// Reads a BSON Int32 from the reader.
127140
/// </summary>

src/MongoDB.Bson/IO/IBsonWriter.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ public interface IBsonWriter : IDisposable
129129
/// </summary>
130130
void WriteEndDocument();
131131

132+
/// <summary>
133+
/// Writes a Guid in Standard representation to the writer.
134+
/// </summary>
135+
/// <param name="guid">The Guid value.</param>
136+
void WriteGuid(Guid guid);
137+
138+
/// <summary>
139+
/// Writes a Guid in the specified representation to the writer.
140+
/// </summary>
141+
/// <param name="guid">The Guid value.</param>
142+
/// <param name="guidRepresentation">The GuidRepresentation.</param>
143+
void WriteGuid(Guid guid, GuidRepresentation guidRepresentation);
144+
132145
/// <summary>
133146
/// Writes a BSON Int32 to the writer.
134147
/// </summary>

src/MongoDB.Bson/IO/JsonReader.cs

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public class JsonReader : BsonReader
6868
private readonly JsonBuffer _buffer;
6969
private readonly JsonReaderSettings _jsonReaderSettings; // same value as in base class just declared as derived class
7070
private JsonReaderContext _context;
71+
private Guid? _currentGuid; // only relevant when _currentValue is a BsonBinaryData instance
7172
private JsonToken _currentToken;
7273
private BsonValue _currentValue;
7374
private JsonToken _pushedToken;
@@ -300,15 +301,15 @@ public override BsonType ReadBsonType()
300301
break;
301302
case "BinData":
302303
CurrentBsonType = BsonType.Binary;
303-
_currentValue = ParseBinDataConstructor();
304+
(_currentValue, _currentGuid) = ParseBinDataConstructor();
304305
break;
305306
case "Date":
306307
CurrentBsonType = BsonType.String;
307308
_currentValue = ParseDateTimeConstructor(false); // withNew = false
308309
break;
309310
case "HexData":
310311
CurrentBsonType = BsonType.Binary;
311-
_currentValue = ParseHexDataConstructor();
312+
(_currentValue, _currentGuid) = ParseHexDataConstructor();
312313
break;
313314
case "ISODate":
314315
CurrentBsonType = BsonType.DateTime;
@@ -356,10 +357,10 @@ public override BsonType ReadBsonType()
356357
case "PYUUID":
357358
case "PYGUID":
358359
CurrentBsonType = BsonType.Binary;
359-
_currentValue = ParseUUIDConstructor(valueToken.Lexeme);
360+
(_currentValue, _currentGuid) = ParseUUIDConstructor(valueToken.Lexeme);
360361
break;
361362
case "new":
362-
CurrentBsonType = ParseNew(out _currentValue);
363+
(CurrentBsonType, _currentValue, _currentGuid) = ParseNew();
363364
break;
364365
default:
365366
noValueFound = true;
@@ -539,6 +540,49 @@ public override void ReadEndDocument()
539540
}
540541
}
541542

543+
/// <inheritdoc/>
544+
public override Guid ReadGuid()
545+
{
546+
if (Disposed) { ThrowObjectDisposedException(); }
547+
VerifyBsonType(nameof(ReadGuid), BsonType.Binary);
548+
State = GetNextState();
549+
550+
if (_currentGuid.HasValue)
551+
{
552+
return _currentGuid.Value;
553+
}
554+
555+
var binaryData = _currentValue.AsBsonBinaryData;
556+
if (binaryData.SubType != BsonBinarySubType.UuidStandard)
557+
{
558+
throw new FormatException($"GuidRepresentation is unknown for binary subtype {binaryData.SubType}.");
559+
}
560+
561+
return GuidConverter.FromBytes(binaryData.Bytes, GuidRepresentation.Standard);
562+
}
563+
564+
/// <inheritdoc/>
565+
public override Guid ReadGuid(GuidRepresentation guidRepresentation)
566+
{
567+
if (Disposed) { ThrowObjectDisposedException(); }
568+
VerifyBsonType(nameof(ReadGuid), BsonType.Binary);
569+
State = GetNextState();
570+
571+
if (_currentGuid.HasValue)
572+
{
573+
return _currentGuid.Value;
574+
}
575+
576+
var binaryData = _currentValue.AsBsonBinaryData;
577+
var expectedSubType = GuidConverter.GetSubType(guidRepresentation);
578+
if (binaryData.SubType != expectedSubType)
579+
{
580+
throw new FormatException($"Expected the binary sub type to be {expectedSubType}, but it was {binaryData.SubType}");
581+
}
582+
583+
return GuidConverter.FromBytes(binaryData.Bytes, guidRepresentation);
584+
}
585+
542586
/// <summary>
543587
/// Reads a BSON Int32 from the reader.
544588
/// </summary>
@@ -935,7 +979,7 @@ private bool IsValidBinaryDataSubTypeString(string value)
935979
HexUtils.IsValidHexString(value);
936980
}
937981

938-
private BsonValue ParseBinDataConstructor()
982+
private (BsonBinaryData, Guid?) ParseBinDataConstructor()
939983
{
940984
VerifyToken("(");
941985
var subTypeToken = PopToken();
@@ -954,10 +998,10 @@ private BsonValue ParseBinDataConstructor()
954998
VerifyToken(")");
955999
var bytes = Convert.FromBase64String(bytesToken.StringValue);
9561000
var subType = (BsonBinarySubType)subTypeToken.Int32Value;
957-
return new BsonBinaryData(bytes, subType);
1001+
return (new BsonBinaryData(bytes, subType), null);
9581002
}
9591003

960-
private BsonValue ParseBinDataExtendedJson()
1004+
private (BsonBinaryData, Guid?) ParseBinDataExtendedJson()
9611005
{
9621006
VerifyToken(":");
9631007

@@ -976,7 +1020,7 @@ private BsonValue ParseBinDataExtendedJson()
9761020

9771021
VerifyToken("}");
9781022

979-
return new BsonBinaryData(bytes, subType);
1023+
return (new BsonBinaryData(bytes, subType), null);
9801024
}
9811025

9821026
private void ParseBinDataExtendedJsonCanonical(out byte[] bytes, out BsonBinarySubType subType)
@@ -1080,7 +1124,7 @@ private void ParseBinDataExtendedJsonLegacy(JsonToken nextToken, out byte[] byte
10801124
}
10811125
}
10821126

1083-
private BsonValue ParseHexDataConstructor()
1127+
private (BsonBinaryData, Guid?) ParseHexDataConstructor()
10841128
{
10851129
VerifyToken("(");
10861130
var subTypeToken = PopToken();
@@ -1100,7 +1144,13 @@ private BsonValue ParseHexDataConstructor()
11001144
var bytes = BsonUtils.ParseHexString(bytesToken.StringValue);
11011145
var subType = (BsonBinarySubType)subTypeToken.Int32Value;
11021146

1103-
return new BsonBinaryData(bytes, subType);
1147+
if ((subType == BsonBinarySubType.UuidStandard || subType == BsonBinarySubType.UuidLegacy) &&
1148+
bytes.Length != 16)
1149+
{
1150+
throw new FormatException($"Length must be 16, not {bytes.Length}, when subType is {subType}.");
1151+
}
1152+
1153+
return (new BsonBinaryData(bytes, subType), null);
11041154
}
11051155

11061156
private BsonType ParseJavaScriptExtendedJson(out BsonValue value)
@@ -1291,7 +1341,7 @@ private BsonType ParseExtendedJson()
12911341
{
12921342
switch (nameToken.StringValue)
12931343
{
1294-
case "$binary": _currentValue = ParseBinDataExtendedJson(); return BsonType.Binary;
1344+
case "$binary": (_currentValue, _currentGuid) = ParseBinDataExtendedJson(); return BsonType.Binary;
12951345
case "$code": return ParseJavaScriptExtendedJson(out _currentValue);
12961346
case "$date": _currentValue = ParseDateTimeExtendedJson(); return BsonType.DateTime;
12971347
case "$maxkey": case "$maxKey": _currentValue = ParseMaxKeyExtendedJson(); return BsonType.MaxKey;
@@ -1311,7 +1361,7 @@ private BsonType ParseExtendedJson()
13111361
case "$symbol": _currentValue = ParseSymbolExtendedJson(); return BsonType.Symbol;
13121362
case "$timestamp": _currentValue = ParseTimestampExtendedJson(); return BsonType.Timestamp;
13131363
case "$undefined": _currentValue = ParseUndefinedExtendedJson(); return BsonType.Undefined;
1314-
case "$uuid": _currentValue = ParseUuidExtendedJson(); return BsonType.Binary;
1364+
case "$uuid": (_currentValue, _currentGuid) = ParseUuidExtendedJson(); return BsonType.Binary;
13151365
}
13161366
}
13171367
ReturnToBookmark(bookmark);
@@ -1491,46 +1541,49 @@ private BsonValue ParseMinKeyExtendedJson()
14911541
return BsonMinKey.Value;
14921542
}
14931543

1494-
private BsonType ParseNew(out BsonValue value)
1544+
private (BsonType, BsonValue, Guid?) ParseNew()
14951545
{
14961546
var typeToken = PopToken();
14971547
if (typeToken.Type != JsonTokenType.UnquotedString)
14981548
{
14991549
var message = string.Format("JSON reader expected a type name but found '{0}'.", typeToken.Lexeme);
15001550
throw new FormatException(message);
15011551
}
1552+
1553+
BsonValue value;
1554+
Guid? guid = null;
15021555
switch (typeToken.Lexeme)
15031556
{
15041557
case "BinData":
1505-
value = ParseBinDataConstructor();
1506-
return BsonType.Binary;
1558+
(value, guid) = ParseBinDataConstructor();
1559+
return (BsonType.Binary, value, guid);
15071560
case "Date":
15081561
value = ParseDateTimeConstructor(true); // withNew = true
1509-
return BsonType.DateTime;
1562+
return (BsonType.DateTime, value, null);
15101563
case "HexData":
1511-
value = ParseHexDataConstructor();
1512-
return BsonType.Binary;
1564+
(value, guid) = ParseHexDataConstructor();
1565+
return (BsonType.Binary, value, guid);
15131566
case "ISODate":
15141567
value = ParseISODateTimeConstructor();
1515-
return BsonType.DateTime;
1568+
return (BsonType.DateTime, value, null);
15161569
case "NumberDecimal":
15171570
value = ParseNumberDecimalConstructor();
1518-
return BsonType.Decimal128;
1571+
return (BsonType.Decimal128, value, null);
15191572
case "NumberInt":
15201573
value = ParseNumberConstructor();
1521-
return BsonType.Int32;
1574+
return (BsonType.Int32, value, null);
15221575
case "NumberLong":
15231576
value = ParseNumberLongConstructor();
1524-
return BsonType.Int64;
1577+
return (BsonType.Int64, value, null);
15251578
case "ObjectId":
15261579
value = ParseObjectIdConstructor();
1527-
return BsonType.ObjectId;
1580+
return (BsonType.ObjectId, value, null);
15281581
case "RegExp":
15291582
value = ParseRegularExpressionConstructor();
1530-
return BsonType.RegularExpression;
1583+
return (BsonType.RegularExpression, value, null);
15311584
case "Timestamp":
15321585
value = ParseTimestampConstructor();
1533-
return BsonType.Timestamp;
1586+
return (BsonType.Timestamp, value, null);
15341587
case "UUID":
15351588
case "GUID":
15361589
case "CSUUID":
@@ -1539,8 +1592,8 @@ private BsonType ParseNew(out BsonValue value)
15391592
case "JGUID":
15401593
case "PYUUID":
15411594
case "PYGUID":
1542-
value = ParseUUIDConstructor(typeToken.Lexeme);
1543-
return BsonType.Binary;
1595+
(value, guid) = ParseUUIDConstructor(typeToken.Lexeme);
1596+
return (BsonType.Binary, value, guid);
15441597
default:
15451598
var message = string.Format("JSON reader expected a type name but found '{0}'.", typeToken.Lexeme);
15461599
throw new FormatException(message);
@@ -2036,7 +2089,7 @@ private BsonValue ParseUndefinedExtendedJson()
20362089
return BsonMaxKey.Value;
20372090
}
20382091

2039-
private BsonValue ParseUuidExtendedJson()
2092+
private (BsonBinaryData, Guid?) ParseUuidExtendedJson()
20402093
{
20412094
VerifyToken(":");
20422095
var uuidToken = PopToken();
@@ -2048,10 +2101,12 @@ private BsonValue ParseUuidExtendedJson()
20482101
VerifyToken("}");
20492102

20502103
var guid = Guid.Parse(uuidToken.StringValue);
2051-
return new BsonBinaryData(guid, GuidRepresentation.Standard);
2104+
var bytes = GuidConverter.ToBytes(guid, GuidRepresentation.Standard);
2105+
2106+
return (new BsonBinaryData(bytes, BsonBinarySubType.UuidStandard), guid);
20522107
}
20532108

2054-
private BsonValue ParseUUIDConstructor(string uuidConstructorName)
2109+
private (BsonBinaryData, Guid?) ParseUUIDConstructor(string uuidConstructorName)
20552110
{
20562111
VerifyToken("(");
20572112
var bytesToken = PopToken();
@@ -2089,7 +2144,7 @@ private BsonValue ParseUUIDConstructor(string uuidConstructorName)
20892144
bytes = GuidConverter.ToBytes(guid, guidRepresentation);
20902145
var subType = GuidConverter.GetSubType(guidRepresentation);
20912146

2092-
return new BsonBinaryData(bytes, subType);
2147+
return (new BsonBinaryData(bytes, subType), guid);
20932148
}
20942149

20952150
private JsonToken PopToken()

0 commit comments

Comments
 (0)