Skip to content

Commit

Permalink
Added BSON Type to mongodb package. (ChilliCream#4498)
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalSenn authored Dec 8, 2021
1 parent f77a2be commit fe50037
Show file tree
Hide file tree
Showing 58 changed files with 2,066 additions and 9 deletions.
396 changes: 396 additions & 0 deletions src/HotChocolate/MongoDb/src/Types/BsonType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,396 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using HotChocolate.Configuration;
using HotChocolate.Language;
using HotChocolate.Types.MongoDb.Resources;
using HotChocolate.Utilities;
using MongoDB.Bson;

namespace HotChocolate.Types.MongoDb;

/// <summary>
/// BSON is a binary format in which zero or more ordered key/value pairs are stored as a single
/// entity.
/// The results are returned as JSON objects
/// </summary>
public class BsonType : ScalarType
{
private ITypeConverter _converter = default!;

/// <summary>
/// Initializes a new instance of the <see cref="BsonType"/> class.
/// </summary>
public BsonType()
: this(
MongoDbScalarNames.Bson,
MongoDbTypesResources.Bson_Type_Description,
BindingBehavior.Implicit)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="BsonType"/> class.
/// </summary>
public BsonType(
NameString name,
string? description = null,
BindingBehavior bind = BindingBehavior.Explicit)
: base(name, bind)
{
SpecifiedBy = new Uri("https://bsonspec.org/spec.html");
Description = description;
}

/// <inheritdoc />
public override Type RuntimeType => typeof(BsonValue);

/// <inheritdoc />
protected override void OnCompleteType(
ITypeCompletionContext context,
IDictionary<string, object?> contextData)
{
_converter = context.Services.GetTypeConverter();
base.OnCompleteType(context, contextData);
}

/// <inheritdoc />
public override bool IsInstanceOfType(IValueNode valueSyntax)
{
if (valueSyntax is null)
{
throw new ArgumentNullException(nameof(valueSyntax));
}

switch (valueSyntax)
{
case StringValueNode:
case IntValueNode:
case FloatValueNode:
case BooleanValueNode:
case ListValueNode:
case ObjectValueNode:
case NullValueNode:
return true;

default:
return false;
}
}

private BsonValue? ParseLiteralToBson(IValueNode literal)
{
switch (literal)
{
case StringValueNode svn:
return new BsonString(svn.Value);

case IntValueNode ivn:
return new BsonInt64(long.Parse(ivn.Value, CultureInfo.InvariantCulture));

case FloatValueNode fvn
when double.TryParse(fvn.Value,
NumberStyles.Float | NumberStyles.Integer,
CultureInfo.InvariantCulture,
out double f):
return new BsonDouble(f);

case FloatValueNode fvn:
return new BsonDecimal128(
decimal.Parse(fvn.Value, CultureInfo.InvariantCulture));

case BooleanValueNode bvn:
return new BsonBoolean(bvn.Value);

case ListValueNode lvn:
BsonValue?[] values = new BsonValue[lvn.Items.Count];
for (var i = 0; i < lvn.Items.Count; i++)
{
values[i] = ParseLiteralToBson(lvn.Items[i]);
}

return new BsonArray(values);

case ObjectValueNode ovn:
BsonDocument document = new();
foreach (ObjectFieldNode field in ovn.Fields)
{
document.Add(field.Name.Value, ParseLiteralToBson(field.Value));
}

return document;

case NullValueNode:
return BsonNull.Value;

default:
throw ThrowHelper.Bson_CouldNotParseLiteral(this, literal);
}
}

/// <inheritdoc />
public override object? ParseLiteral(IValueNode valueSyntax)
{
return ParseLiteralToBson(valueSyntax);
}

/// <inheritdoc />
public override IValueNode ParseValue(object? runtimeValue)
{
if (runtimeValue is null)
{
return NullValueNode.Default;
}

if (runtimeValue is not BsonValue value)
{
value = BsonTypeMapper.MapToBsonValue(runtimeValue);
}

switch (value)
{
case BsonString s:
return new StringValueNode(s.Value);

case BsonInt32 i:
return new IntValueNode(i.Value);

case BsonInt64 l:
return new IntValueNode(l.Value);

case BsonDouble f:
return new FloatValueNode(f.Value);

// The range of Decimal128 is different. Therefor we have to serialize
// it as a string, or else information loss could occure
// see https://jira.mongodb.org/browse/CSHARP-2210
case BsonDecimal128 d:
return new StringValueNode(d.Value.ToString());

case BsonBoolean b:
return new BooleanValueNode(b.Value);

case BsonObjectId s:
return new StringValueNode(s.Value.ToString());

case BsonDateTime dateTime when _converter
.TryConvert(dateTime.ToNullableUniversalTime(), out string formatedDateTime):
return new StringValueNode(formatedDateTime);

case BsonBinaryData bd:
return new StringValueNode(Convert.ToBase64String(bd.Bytes));

case BsonTimestamp timeStamp:
return new IntValueNode(timeStamp.Value);
}

if (value is BsonDocument doc)
{
List<ObjectFieldNode> fields = new();
foreach (BsonElement field in doc)
{
fields.Add(new ObjectFieldNode(field.Name, ParseValue(field.Value)));
}

return new ObjectValueNode(fields);
}

if (value is BsonArray arr)
{
List<IValueNode> valueList = new();
foreach (BsonValue element in arr)
{
valueList.Add(ParseValue(element));
}

return new ListValueNode(valueList);
}

var mappedValue = BsonTypeMapper.MapToDotNetValue(value);
Type type = mappedValue.GetType();

if (type.IsValueType &&
_converter.TryConvert(type, typeof(string), mappedValue, out object? converted) &&
converted is string c)
{
return new StringValueNode(c);
}

throw ThrowHelper.Bson_CouldNotParseValue(this, runtimeValue);
}

/// <inheritdoc />
public override IValueNode ParseResult(object? resultValue) =>
ParseValue(resultValue);

public override bool TrySerialize(object? runtimeValue, out object? resultValue)
{
resultValue = null;
if (runtimeValue is null or BsonNull)
{
return true;
}

switch (runtimeValue)
{
case BsonArray arr:
object?[] res = new object?[arr.Count];
for (var i = 0; i < arr.Count; i++)
{
if (!TrySerialize(arr[i], out var s))
{
return false;
}

res[i] = s;
}

resultValue = res;
return true;

case BsonDocument doc:
Dictionary<string, object?> docRes = new();
foreach (BsonElement element in doc)
{
if (!TrySerialize(element.Value, out var s))
{
return false;
}

docRes[element.Name] = s;
}

resultValue = docRes;
return true;

case BsonDateTime dateTime:
DateTime? parsedDateTime = dateTime.ToNullableUniversalTime();
if (_converter.TryConvert(parsedDateTime, out string? formatedDateTime))
{
resultValue = formatedDateTime;
return true;
}

return false;

case BsonTimestamp timeStamp:
resultValue = timeStamp.Value;
return true;

case BsonObjectId objectId:
resultValue = objectId.Value.ToString();
return true;

case BsonString s:
resultValue = s.Value;
return true;

case BsonInt32 i:
resultValue = i.Value;
return true;

case BsonInt64 l:
resultValue = l.Value;
return true;

case BsonDouble f:
resultValue = f.Value;
return true;

case BsonBinaryData bd:
resultValue = Convert.ToBase64String(bd.Bytes);
return true;

// The range of Decimal128 is different. Therefor we have to serialize
// it as a string, or else information loss could occure
// see https://jira.mongodb.org/browse/CSHARP-2210
case BsonDecimal128 d:
resultValue = d.Value.ToString();
return true;

case BsonBoolean b:
resultValue = b.Value;
return true;

case BsonValue a:
var dotNetValue = BsonTypeMapper.MapToDotNetValue(a);

Type type = dotNetValue.GetType();

if (type.IsValueType &&
_converter.TryConvert(type, typeof(string), dotNetValue, out object? c) &&
c is string casted)
{
resultValue = casted;
return true;
}

resultValue = null;
return false;

case IValueNode literal:
resultValue = ParseLiteral(literal);
return true;

default:
resultValue = null;
return false;
}
}

/// <inheritdoc />
public override bool TryDeserialize(object? resultValue, out object? runtimeValue)
{
object? elementValue;
runtimeValue = null;
switch (resultValue)
{
case IDictionary<string, object> dictionary:
{
var result = new BsonDocument();
foreach (KeyValuePair<string, object> element in dictionary)
{
if (TryDeserialize(element.Value, out elementValue))
{
result[element.Key] = (BsonValue)elementValue;
}
else
{
return false;
}
}

runtimeValue = result;
return true;
}

case IList list:
{
var result = new BsonValue[list.Count];
for (var i = 0; i < list.Count; i++)
{
if (TryDeserialize(list[i], out elementValue))
{
result[i] = (BsonValue)elementValue;
}
else
{
return false;
}
}

runtimeValue = new BsonArray(result);
return true;
}

case IValueNode literal:
runtimeValue = ParseLiteral(literal);
return true;

default:
runtimeValue = BsonTypeMapper.MapToBsonValue(resultValue);
return true;
}
}
}
Loading

0 comments on commit fe50037

Please sign in to comment.