Skip to content

Commit

Permalink
Support nullable fields.
Browse files Browse the repository at this point in the history
  • Loading branch information
ejball committed Nov 3, 2022
1 parent 86a273a commit 0fd6e30
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 1 deletion.
2 changes: 2 additions & 0 deletions example/ExampleApi.fsd
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ service ExampleApi

[tag(name: widgets)]
namedWidgets: map<Widget>;

ternary: nullable<boolean>;
}

/// Identifies a widget field.
Expand Down
2 changes: 2 additions & 0 deletions example/ExampleApi.fsd.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ data Preference
[tag(name: widgets)]
namedWidgets: map<Widget>;
ternary: nullable<boolean>;
}
```

Expand Down
2 changes: 2 additions & 0 deletions example/output/ExampleApi-nowidgets.fsd
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ service ExampleApi
objects: object[];

namedStrings: map<string>;

ternary: nullable<boolean>;
}

/// An obsolete DTO.
Expand Down
2 changes: 2 additions & 0 deletions example/output/ExampleApi.fsd
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ service ExampleApi

[tag(name: widgets)]
namedWidgets: map<Widget>;

ternary: nullable<boolean>;
}

/// Identifies a widget field.
Expand Down
15 changes: 14 additions & 1 deletion src/Facility.Definition/ServiceTypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public sealed class ServiceTypeInfo
/// </summary>
public static ServiceTypeInfo CreatePrimitive(ServiceTypeKind kind)
{
if (kind == ServiceTypeKind.Dto || kind == ServiceTypeKind.Enum || kind == ServiceTypeKind.Result || kind == ServiceTypeKind.Array || kind == ServiceTypeKind.Map)
if (kind == ServiceTypeKind.Dto || kind == ServiceTypeKind.Enum || kind == ServiceTypeKind.Result || kind == ServiceTypeKind.Array || kind == ServiceTypeKind.Map || kind == ServiceTypeKind.Nullable)
throw new ArgumentOutOfRangeException(nameof(kind), "Kind must be primitive.");
return new ServiceTypeInfo(kind);
}
Expand Down Expand Up @@ -40,6 +40,11 @@ public static ServiceTypeInfo CreatePrimitive(ServiceTypeKind kind)
/// </summary>
public static ServiceTypeInfo CreateMap(ServiceTypeInfo valueType) => new(ServiceTypeKind.Map, valueType: valueType);

/// <summary>
/// Create a nullable type.
/// </summary>
public static ServiceTypeInfo CreateNullable(ServiceTypeInfo valueType) => new(ServiceTypeKind.Nullable, valueType: valueType);

/// <summary>
/// The kind of type.
/// </summary>
Expand Down Expand Up @@ -72,6 +77,7 @@ public override string ToString()
ServiceTypeKind.Result => $"result<{ValueType}>",
ServiceTypeKind.Array => $"{ValueType}[]",
ServiceTypeKind.Map => $"map<{ValueType}>",
ServiceTypeKind.Nullable => $"nullable<{ValueType}>",
_ => s_primitives.Where(x => x.Kind == Kind).Select(x => x.Name).Single(),
};
}
Expand Down Expand Up @@ -106,6 +112,13 @@ public override string ToString()
return valueType == null ? null : CreateMap(valueType);
}

var nullableValueType = TryPrefixSuffix(text, "nullable<", ">");
if (nullableValueType != null)
{
var valueType = TryParse(nullableValueType, findMember);
return valueType == null || valueType.Kind == ServiceTypeKind.Nullable ? null : CreateNullable(valueType);
}

var member = findMember(text);
if (member != null)
{
Expand Down
5 changes: 5 additions & 0 deletions src/Facility.Definition/ServiceTypeKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,9 @@ public enum ServiceTypeKind
/// A map.
/// </summary>
Map,

/// <summary>
/// A nullable.
/// </summary>
Nullable,
}
38 changes: 38 additions & 0 deletions tests/Facility.Definition.UnitTests/FieldTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ public void ResultOfDto()
[TestCase("result<Dto>", ServiceTypeKind.Result)]
[TestCase("int32[]", ServiceTypeKind.Array)]
[TestCase("map<int32>", ServiceTypeKind.Map)]
[TestCase("nullable<int32>", ServiceTypeKind.Nullable)]
public void ArrayOfAnything(string name, ServiceTypeKind kind)
{
var service = TestUtility.ParseTestApi("service TestApi { enum Enum { x, y } data Dto { x: xyzzy[]; } }".Replace("xyzzy", name));
Expand Down Expand Up @@ -224,6 +225,7 @@ public void ArrayOfAnything(string name, ServiceTypeKind kind)
[TestCase("result<Dto>", ServiceTypeKind.Result)]
[TestCase("int32[]", ServiceTypeKind.Array)]
[TestCase("map<int32>", ServiceTypeKind.Map)]
[TestCase("nullable<int32>", ServiceTypeKind.Nullable)]
public void MapOfAnything(string name, ServiceTypeKind kind)
{
var service = TestUtility.ParseTestApi("service TestApi { enum Enum { x, y } data Dto { x: map<xyzzy>; } }".Replace("xyzzy", name));
Expand Down Expand Up @@ -252,6 +254,7 @@ public void MapOfAnything(string name, ServiceTypeKind kind)
[TestCase("result<Dto>", ServiceTypeKind.Result)]
[TestCase("int32[]", ServiceTypeKind.Array)]
[TestCase("map<int32>", ServiceTypeKind.Map)]
[TestCase("nullable<int32>", ServiceTypeKind.Nullable)]
public void ResultOfAnything(string name, ServiceTypeKind kind)
{
var service = TestUtility.ParseTestApi("service TestApi { enum Enum { x, y } data Dto { x: result<xyzzy>; } }".Replace("xyzzy", name));
Expand All @@ -266,6 +269,41 @@ public void ResultOfAnything(string name, ServiceTypeKind kind)
type.ValueType!.Kind.Should().Be(kind);
}

[TestCase("string", ServiceTypeKind.String)]
[TestCase("boolean", ServiceTypeKind.Boolean)]
[TestCase("double", ServiceTypeKind.Double)]
[TestCase("int32", ServiceTypeKind.Int32)]
[TestCase("int64", ServiceTypeKind.Int64)]
[TestCase("decimal", ServiceTypeKind.Decimal)]
[TestCase("bytes", ServiceTypeKind.Bytes)]
[TestCase("object", ServiceTypeKind.Object)]
[TestCase("error", ServiceTypeKind.Error)]
[TestCase("Dto", ServiceTypeKind.Dto)]
[TestCase("Enum", ServiceTypeKind.Enum)]
[TestCase("result<Dto>", ServiceTypeKind.Result)]
[TestCase("int32[]", ServiceTypeKind.Array)]
[TestCase("map<int32>", ServiceTypeKind.Map)]
public void NullableOfAnything(string name, ServiceTypeKind kind)
{
var service = TestUtility.ParseTestApi("service TestApi { enum Enum { x, y } data Dto { x: nullable<xyzzy>; } }".Replace("xyzzy", name));

var dto = service.Dtos.Single();
var field = dto.Fields.Single();
field.Name.Should().Be("x");
field.Attributes.Count.Should().Be(0);
field.Summary.Should().Be("");
var type = service.GetFieldType(field)!;
type.Kind.Should().Be(ServiceTypeKind.Nullable);
type.ValueType!.Kind.Should().Be(kind);
}

[Test]
public void NullableOfNullable()
{
var errors = TestUtility.TryParseInvalidTestApi("service TestApi { enum Enum { x, y } data Dto { x: nullable<nullable<int32>>; } }");
errors.Single().Message.Should().Be("Unknown field type 'nullable<nullable<int32>>'.");
}

[Test]
public void FieldParts()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ public void BodyResponseField(string type)
[TestCase("int64")]
[TestCase("decimal")]
[TestCase("Enum")]
[TestCase("nullable<string>")]
[TestCase("nullable<Dto>")]
public void BodyResponseFieldInvalidType(string type)
{
ParseInvalidHttpApi("service TestApi { data Dto {} enum Enum { x } method do {}: { [http(from: body)] id: xyzzy; } }".Replace("xyzzy", type))
Expand Down Expand Up @@ -623,6 +625,7 @@ public void SimpleFieldTypeSupported(string type)
[TestCase("result<string>")]
[TestCase("string[][]")]
[TestCase("Thing")]
[TestCase("nullable<string>")]
public void NonSimpleFieldTypeNotSupported(string type)
{
ParseInvalidHttpApi("service TestApi { [http(method: get, path: \"/xyzzy/{id}\")] method do { id: _; }: {} data Thing { id: string; } }".Replace("_", type))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public void EnumType()
[TestCase("result<MyDto>", ServiceTypeKind.Result)]
[TestCase("MyDto[]", ServiceTypeKind.Array)]
[TestCase("map<MyDto>", ServiceTypeKind.Map)]
[TestCase("nullable<MyDto>", ServiceTypeKind.Nullable)]
public void ContainerOfDtoType(string name, ServiceTypeKind kind)
{
var service = new ServiceInfo(name: "MyApi",
Expand Down

0 comments on commit 0fd6e30

Please sign in to comment.