Skip to content

Commit 65f0dd6

Browse files
committed
feat: Add support for select custom field type
1 parent 82c669f commit 65f0dd6

File tree

16 files changed

+216
-16
lines changed

16 files changed

+216
-16
lines changed

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,50 @@ serviceCollection.AddPaperlessDotNet(
8484
For a working example
8585
see [unit tests](tests/VMelnalksnis.PaperlessDotNet.Tests/Serialization/CustomFieldConverterTests.cs)
8686
and [integration tests](tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/Documents/DocumentClientTests.cs).
87+
88+
### Select custom fields
89+
Version `2.11.0` introduced `select` custom fields, which require additional setup in order to serialize/deserialize properly.
90+
First, you'll need to define `SmartEnum` class for each `select` custom field:
91+
```csharp
92+
public sealed class SelectOptions : SmartEnum<SelectOptions>
93+
{
94+
public static readonly SelectOptions Option1 = new("First option", 0);
95+
public static readonly SelectOptions Option2 = new("Second option", 1);
96+
97+
private SelectOptions(string name, int value)
98+
: base(name, value)
99+
{
100+
}
101+
}
102+
```
103+
**NOTE:** the values **MUST** be sequential and start at 0 in order to match how they are stored in paperless.
104+
105+
Then you can add the property to your `CustomFields` class
106+
```csharp
107+
public SelectOptions? Field9 { get; set; }
108+
```
109+
and add the `SmartEnumValueConverter<TEnum, TValue>` in one of the possible ways:
110+
```csharp
111+
[JsonConverter(typeof(SmartEnumValueConverter<SelectOptions, int>))]
112+
public SelectOptions? Field9 { get; set; }
113+
```
114+
or
115+
```csharp
116+
serviceCollection.AddPaperlessDotNet(
117+
configuration,
118+
options =>
119+
{
120+
options.Options.Converters.Add(new SmartEnumValueConverter<SelectOptions, int>())
121+
options.Options.Converters.Add(new CustomFieldsConverter<CustomFields>(options));
122+
options.Options.TypeInfoResolverChain.Add(SerializerContext.Default);
123+
});
124+
```
125+
or
126+
```csharp
127+
[JsonSourceGenerationOptions(Converters = [typeof(SmartEnumValueConverter<SelectOptions, int>)])]
128+
[JsonSerializable(typeof(Document<TestCustomFields>))]
129+
internal sealed partial class TestSerializerContext : JsonSerializerContext;
130+
```
131+
132+
In order to create a `select` custom field, you also need to use
133+
either `SelectCustomFieldCreation<TEnum, TValue>` or `SelectCustomFieldCreation<TEnum>` and add it to the `JsonSerializerContext`.

source/VMelnalksnis.PaperlessDotNet/Documents/CustomField.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ public sealed class CustomField
1818

1919
/// <summary>Gets or sets the type of the custom field.</summary>
2020
public CustomFieldType DataType { get; set; } = null!;
21+
22+
/// <summary>Gets or sets extra data about the custom field.</summary>
23+
public CustomFieldExtraData? ExtraData { get; set; }
2124
}

source/VMelnalksnis.PaperlessDotNet/Documents/CustomFieldCreation.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,47 @@
22
// Licensed under the Apache License 2.0.
33
// See LICENSE file in the project root for full license information.
44

5+
using System;
56
using System.Diagnostics;
67

78
namespace VMelnalksnis.PaperlessDotNet.Documents;
89

910
/// <summary>Information needed to create a <see cref="CustomField"/>.</summary>
10-
/// <param name="name">The name of the custom field.</param>
11-
/// <param name="dataType">The type of the custom field.</param>
1211
[DebuggerDisplay("{Name} ({DataType.Name})")]
13-
public sealed class CustomFieldCreation(string name, CustomFieldType dataType)
12+
public class CustomFieldCreation
1413
{
14+
/// <summary>Initializes a new instance of the <see cref="CustomFieldCreation"/> class.</summary>
15+
/// <param name="name">The name of the custom field.</param>
16+
/// <param name="dataType">The type of the custom field.</param>
17+
public CustomFieldCreation(string name, CustomFieldType dataType)
18+
: this(name, dataType, null)
19+
{
20+
Name = name;
21+
DataType = dataType;
22+
}
23+
24+
/// <summary>Initializes a new instance of the <see cref="CustomFieldCreation"/> class.</summary>
25+
/// <param name="name">The name of the custom field.</param>
26+
/// <param name="dataType">The type of the custom field.</param>
27+
/// <param name="extraData">Extra data about the custom field.</param>
28+
protected CustomFieldCreation(string name, CustomFieldType dataType, CustomFieldExtraData? extraData)
29+
{
30+
if (dataType == CustomFieldType.Select && extraData?.SelectOptions is not { Length: > 0 })
31+
{
32+
throw new ArgumentOutOfRangeException(nameof(dataType), dataType, "Must specify select options");
33+
}
34+
35+
Name = name;
36+
DataType = dataType;
37+
ExtraData = extraData;
38+
}
39+
1540
/// <summary>Gets the name of the custom field.</summary>
16-
public string Name { get; } = name;
41+
public string Name { get; }
1742

1843
/// <summary>Gets the type of the custom field.</summary>
19-
public CustomFieldType DataType { get; } = dataType;
44+
public CustomFieldType DataType { get; }
45+
46+
/// <summary>Gets extra data about the field.</summary>
47+
public CustomFieldExtraData? ExtraData { get; }
2048
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2022 Valters Melnalksnis
2+
// Licensed under the Apache License 2.0.
3+
// See LICENSE file in the project root for full license information.
4+
5+
namespace VMelnalksnis.PaperlessDotNet.Documents;
6+
7+
/// <summary>Extra data about a <see cref="CustomField"/>.</summary>
8+
public sealed class CustomFieldExtraData
9+
{
10+
/// <summary>Gets or sets the option names for custom fields of type <see cref="CustomFieldType.Select"/>.</summary>
11+
public string?[] SelectOptions { get; set; } = null!;
12+
13+
/// <summary>Gets or sets the default currency for custom fields of type <see cref="CustomFieldType.Monetary"/>.</summary>
14+
public string? DefaultCurrency { get; set; } = null!;
15+
}

source/VMelnalksnis.PaperlessDotNet/Documents/CustomFieldType.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public sealed class CustomFieldType : SmartEnum<CustomFieldType>
4949
/// <seealso cref="int"/>
5050
public static readonly CustomFieldType DocumentLink = new("documentlink", 8);
5151

52+
/// <summary>A pre-defined list of strings from which the user can choose.</summary>
53+
public static readonly CustomFieldType Select = new("select", 9);
54+
5255
private CustomFieldType(string name, int value)
5356
: base(name, value)
5457
{
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2022 Valters Melnalksnis
2+
// Licensed under the Apache License 2.0.
3+
// See LICENSE file in the project root for full license information.
4+
5+
using System.Linq;
6+
7+
using Ardalis.SmartEnum;
8+
9+
namespace VMelnalksnis.PaperlessDotNet.Documents;
10+
11+
/// <summary>Information needed to create a <see cref="CustomField"/> with type <see cref="CustomFieldType.Select"/>.</summary>
12+
/// <typeparam name="TEnum">The enum type.</typeparam>
13+
public class SelectCustomFieldCreation<TEnum> : CustomFieldCreation
14+
where TEnum : SmartEnum<TEnum, int>
15+
{
16+
private static readonly string[] Names = SmartEnum<TEnum, int>
17+
.List
18+
.Select(smartEnum => smartEnum.Name)
19+
.ToArray();
20+
21+
/// <summary>Initializes a new instance of the <see cref="SelectCustomFieldCreation{TEnum}"/> class.</summary>
22+
/// <param name="name">The name of the custom field.</param>
23+
public SelectCustomFieldCreation(string name)
24+
: base(name, CustomFieldType.Select, new() { SelectOptions = Names })
25+
{
26+
}
27+
}

source/VMelnalksnis.PaperlessDotNet/Serialization/CustomFieldsConverter.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,18 @@ public override void Write(Utf8JsonWriter writer, TFields value, JsonSerializerO
7878
writer.WriteStartObject();
7979
writer.WritePropertyName("value");
8080

81-
if (customField.DataType == CustomFieldType.String || customField.DataType == CustomFieldType.Url || customField.DataType == CustomFieldType.Date)
81+
if (customField.DataType == CustomFieldType.String ||
82+
customField.DataType == CustomFieldType.Url ||
83+
customField.DataType == CustomFieldType.Date)
8284
{
8385
writer.WriteStringValue(property.Value.GetString());
8486
}
8587
else if (customField.DataType == CustomFieldType.Boolean)
8688
{
8789
writer.WriteBooleanValue(property.Value.GetBoolean());
8890
}
89-
else if (customField.DataType == CustomFieldType.Integer)
91+
else if (customField.DataType == CustomFieldType.Integer ||
92+
customField.DataType == CustomFieldType.Select)
9093
{
9194
writer.WriteNumberValue(property.Value.GetInt32());
9295
}

tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/Documents/CustomFields.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// See LICENSE file in the project root for full license information.
44

55
using System;
6+
using System.Text.Json.Serialization;
7+
8+
using Ardalis.SmartEnum.SystemTextJson;
69

710
using NodaTime;
811

@@ -25,4 +28,7 @@ internal sealed class CustomFields
2528
public float? Field7 { get; set; }
2629

2730
public int[]? Field8 { get; set; }
31+
32+
[JsonConverter(typeof(SmartEnumValueConverter<SelectOptions, int>))]
33+
public SelectOptions? Field9 { get; set; }
2834
}

tests/VMelnalksnis.PaperlessDotNet.Tests.Integration/Documents/DocumentClientTests.cs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,30 @@ public async Task CustomFields()
188188

189189
const string documentName = "Lorem Ipsum 2.txt";
190190

191-
await Client.Documents.CreateCustomField(new("field1", CustomFieldType.String));
192-
await Client.Documents.CreateCustomField(new("field2", CustomFieldType.Url));
193-
await Client.Documents.CreateCustomField(new("field3", CustomFieldType.Date));
194-
await Client.Documents.CreateCustomField(new("field4", CustomFieldType.Boolean));
195-
await Client.Documents.CreateCustomField(new("field5", CustomFieldType.Integer));
196-
await Client.Documents.CreateCustomField(new("field6", CustomFieldType.Float));
197-
await Client.Documents.CreateCustomField(new("field7", CustomFieldType.Monetary));
198-
await Client.Documents.CreateCustomField(new("field8", CustomFieldType.DocumentLink));
191+
var fieldCreations = new List<CustomFieldCreation>
192+
{
193+
new("field1", CustomFieldType.String),
194+
new("field2", CustomFieldType.Url),
195+
new("field3", CustomFieldType.Date),
196+
new("field4", CustomFieldType.Boolean),
197+
new("field5", CustomFieldType.Integer),
198+
new("field6", CustomFieldType.Float),
199+
new("field7", CustomFieldType.Monetary),
200+
new("field8", CustomFieldType.DocumentLink),
201+
};
202+
203+
if (PaperlessVersion >= new Version(2, 11, 0))
204+
{
205+
fieldCreations.Add(new SelectCustomFieldCreation<SelectOptions>("field9"));
206+
}
207+
208+
foreach (var customFieldCreation in fieldCreations)
209+
{
210+
await Client.Documents.CreateCustomField(customFieldCreation);
211+
}
199212

200213
var customFields = await Client.Documents.GetCustomFields().ToListAsync();
201-
customFields.Should().HaveCount(8);
214+
customFields.Should().HaveCount(fieldCreations.Count);
202215

203216
var paginatedCustomFields = await Client.Documents.GetCustomFields(1).ToListAsync();
204217
paginatedCustomFields.Should().BeEquivalentTo(customFields);
@@ -230,6 +243,11 @@ public async Task CustomFields()
230243
},
231244
};
232245

246+
if (PaperlessVersion >= new Version(2, 11, 0))
247+
{
248+
update.CustomFields.Field9 = SelectOptions.Option1;
249+
}
250+
233251
SerializerOptions.CustomFields.Clear();
234252
document = await Client.Documents.Update(id, update);
235253

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2022 Valters Melnalksnis
2+
// Licensed under the Apache License 2.0.
3+
// See LICENSE file in the project root for full license information.
4+
5+
using Ardalis.SmartEnum;
6+
7+
namespace VMelnalksnis.PaperlessDotNet.Tests.Integration.Documents;
8+
9+
public sealed class SelectOptions : SmartEnum<SelectOptions>
10+
{
11+
public static readonly SelectOptions Option1 = new("First option", 0);
12+
public static readonly SelectOptions Option2 = new("Second option", 1);
13+
14+
private SelectOptions(string name, int value)
15+
: base(name, value)
16+
{
17+
}
18+
}

0 commit comments

Comments
 (0)