Skip to content

Commit 9d1e073

Browse files
authored
feat: add support for script results in all response types (#415)
Fixes: #116.
1 parent 2e6eadc commit 9d1e073

File tree

13 files changed

+655
-62
lines changed

13 files changed

+655
-62
lines changed

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,40 @@ var results = await client.FindAsync(toFind, FMRecordIdMapper);
181181
// results is IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.
182182
```
183183

184-
### Find with Data Info
184+
### Find with Data Info and Script Results
185185

186186
```csharp
187187
var toFind = new Model { Name = "someName" };
188188
var req = new FindRequest<Model>() { Layout = layout };
189189
req.AddQuery(toFind, false);
190-
var (data, info) = await fdc.SendAsync(req, true);
190+
var (data, info, scriptResponse) = await fdc.SendAsync(req, true);
191+
// scriptResponse.ScriptResult contains the post-request script result
192+
// scriptResponse.ScriptErrorPreRequest / ScriptResultPreRequest for pre-request scripts
193+
// scriptResponse.ScriptErrorPreSort / ScriptResultPreSort for pre-sort scripts
194+
```
195+
196+
### Running Scripts with Requests
197+
198+
All operations (Create, Edit, Delete, Find) return script results when scripts are specified on the request.
199+
200+
```csharp
201+
// Create with scripts
202+
var response = await client.CreateAsync(input, "MyScript", "param",
203+
"PreRequestScript", "preReqParam", "PreSortScript", "preSortParam");
204+
// response.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort
205+
206+
// Edit with scripts
207+
var editResponse = await client.EditAsync(recordId, "MyScript", "param", input);
208+
// editResponse.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort
209+
210+
// Delete with scripts (via IDeleteRequest)
211+
var deleteReq = client.GenerateDeleteRequest();
212+
deleteReq.Layout = "layout";
213+
deleteReq.RecordId = recordId;
214+
deleteReq.Script = "MyScript";
215+
deleteReq.ScriptParameter = "param";
216+
var deleteResponse = await client.SendAsync(deleteReq);
217+
// deleteResponse.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort
191218
```
192219

193220
Alternatively, if you create a calculated field `Get(RecordID)` and put it on your layout then map it the normal way.

docs/example-use.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,40 @@ var results = await client.FindAsync(toFind, FMRecordIdMapper);
137137
// results is IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.
138138
```
139139

140-
### Find with Data Info
140+
### Find with Data Info and Script Results
141141

142142
```csharp
143143
var toFind = new Model { Name = "someName" };
144144
var req = new FindRequest<Model>() { Layout = layout };
145145
req.AddQuery(toFind, false);
146-
var (data, info) = await fdc.SendAsync(req, true);
146+
var (data, info, scriptResponse) = await fdc.SendAsync(req, true);
147+
// scriptResponse.ScriptResult contains the post-request script result
148+
// scriptResponse.ScriptErrorPreRequest / ScriptResultPreRequest for pre-request scripts
149+
// scriptResponse.ScriptErrorPreSort / ScriptResultPreSort for pre-sort scripts
150+
```
151+
152+
### Running Scripts with Requests
153+
154+
All operations (Create, Edit, Delete, Find) return script results when scripts are specified on the request.
155+
156+
```csharp
157+
// Create with scripts
158+
var response = await client.CreateAsync(input, "MyScript", "param",
159+
"PreRequestScript", "preReqParam", "PreSortScript", "preSortParam");
160+
// response.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort
161+
162+
// Edit with scripts
163+
var editResponse = await client.EditAsync(recordId, "MyScript", "param", input);
164+
// editResponse.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort
165+
166+
// Delete with scripts (via IDeleteRequest)
167+
var deleteReq = client.GenerateDeleteRequest();
168+
deleteReq.Layout = "layout";
169+
deleteReq.RecordId = recordId;
170+
deleteReq.Script = "MyScript";
171+
deleteReq.ScriptParameter = "param";
172+
var deleteResponse = await client.SendAsync(deleteReq);
173+
// deleteResponse.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort
147174
```
148175

149176
Alternatively, if you create a calculated field `Get(RecordID)` and put it on your layout then map it the normal way.

src/FMData.Rest/FileMakerRestClient.cs

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,12 @@ public override async Task<ICreateResponse> SendAsync<T>(ICreateRequest<T> req)
485485
try
486486
{
487487
var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
488-
return JsonConvert.DeserializeObject<CreateResponse>(responseJson);
488+
var responseObject = JsonConvert.DeserializeObject<CreateResponse>(responseJson);
489+
490+
var joResponse = JObject.Parse(responseJson);
491+
PopulateScriptResults(responseObject.Response, joResponse["response"]);
492+
493+
return responseObject;
489494
}
490495
catch (Exception ex)
491496
{
@@ -532,6 +537,9 @@ public override async Task<IEditResponse> SendAsync<T>(IEditRequest<T> req)
532537
var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
533538
var responseObject = JsonConvert.DeserializeObject<EditResponse>(responseJson);
534539

540+
var joResponse = JObject.Parse(responseJson);
541+
PopulateScriptResults(responseObject.Response, joResponse["response"]);
542+
535543
return responseObject;
536544
}
537545
catch (Exception ex)
@@ -546,7 +554,7 @@ public override async Task<IEditResponse> SendAsync<T>(IEditRequest<T> req)
546554
/// </summary>
547555
/// <param name="req">The delete record request.</param>
548556
/// <returns></returns>
549-
public override async Task<IResponse> SendAsync(IDeleteRequest req)
557+
public override async Task<IDeleteResponse> SendAsync(IDeleteRequest req)
550558
{
551559
if (string.IsNullOrEmpty(req.Layout)) throw new ArgumentException("Layout is required on the request.");
552560
if (req.RecordId == 0) throw new ArgumentException("RecordId is required on the request and must not be zero.");
@@ -555,7 +563,7 @@ public override async Task<IResponse> SendAsync(IDeleteRequest req)
555563

556564
if (response.StatusCode == HttpStatusCode.NotFound)
557565
{
558-
return new BaseResponse("404", "Error");
566+
return new Responses.DeleteResponse { Messages = new List<ResponseMessage> { new ResponseMessage { Code = "404", Message = "Error" } } };
559567
}
560568

561569
if (response.StatusCode == HttpStatusCode.InternalServerError)
@@ -576,7 +584,11 @@ public override async Task<IResponse> SendAsync(IDeleteRequest req)
576584
try
577585
{
578586
var responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
579-
var responseObject = JsonConvert.DeserializeObject<BaseResponse>(responseJson);
587+
var responseObject = JsonConvert.DeserializeObject<Responses.DeleteResponse>(responseJson);
588+
589+
var joResponse = JObject.Parse(responseJson);
590+
PopulateScriptResults(responseObject.Response, joResponse["response"]);
591+
580592
return responseObject;
581593
}
582594
catch (Exception ex)
@@ -587,7 +599,7 @@ public override async Task<IResponse> SendAsync(IDeleteRequest req)
587599
}
588600

589601
/// <inheritdoc />
590-
public override async Task<(IEnumerable<TResponse>, DataInfoModel)> SendFindRequestAsync<TResponse, TRequest>(
602+
public override async Task<(IEnumerable<TResponse>, DataInfoModel, ActionResponse)> SendFindRequestAsync<TResponse, TRequest>(
591603
IFindRequest<TRequest> req,
592604
Func<TResponse, int, object> fmId = null,
593605
Func<TResponse, int, object> modId = null)
@@ -612,16 +624,29 @@ public override async Task<IResponse> SendAsync(IDeleteRequest req)
612624

613625
var joResponse = JObject.Parse(responseJson);
614626

627+
var responseToken = joResponse["response"];
628+
615629
// get JSON result objects into a list
616-
IList<JToken> results = joResponse["response"]["data"].Children().ToList();
630+
IList<JToken> results = responseToken["data"].Children().ToList();
617631

618632
DataInfoModel dataInfo = null;
619-
var infoDataToken = joResponse["response"]["dataInfo"];
633+
var infoDataToken = responseToken["dataInfo"];
620634
if (infoDataToken != null)
621635
{
622636
dataInfo = infoDataToken.ToObject<DataInfoModel>();
623637
}
624638

639+
// extract script results from response
640+
var scriptResponse = new ActionResponse
641+
{
642+
ScriptError = responseToken["scriptError"]?.ToObject<int>() ?? 0,
643+
ScriptResult = responseToken["scriptResult"]?.ToString(),
644+
ScriptErrorPreRequest = responseToken["scriptError.prerequest"]?.ToObject<int>() ?? 0,
645+
ScriptResultPreRequest = responseToken["scriptResult.prerequest"]?.ToString(),
646+
ScriptErrorPreSort = responseToken["scriptError.presort"]?.ToObject<int>() ?? 0,
647+
ScriptResultPreSort = responseToken["scriptResult.presort"]?.ToString(),
648+
};
649+
625650
// serialize JSON results into .NET objects
626651
IList<TResponse> searchResults = new List<TResponse>();
627652
foreach (var result in results)
@@ -638,7 +663,7 @@ public override async Task<IResponse> SendAsync(IDeleteRequest req)
638663
await ProcessContainers(searchResults).ConfigureAwait(false);
639664
}
640665

641-
return (searchResults, dataInfo);
666+
return (searchResults, dataInfo, scriptResponse);
642667
}
643668

644669
if (response.StatusCode == HttpStatusCode.InternalServerError)
@@ -651,7 +676,7 @@ public override async Task<IResponse> SendAsync(IDeleteRequest req)
651676
if (responseObject.Messages.Any(m => m.Code == "401"))
652677
{
653678
// FileMaker no records match the find request => empty list.
654-
return (new List<TResponse>(), new DataInfoModel());
679+
return (new List<TResponse>(), new DataInfoModel(), null);
655680
}
656681
// throw FMDataException for anything not a 401.
657682
throw new FMDataException(
@@ -663,7 +688,7 @@ public override async Task<IResponse> SendAsync(IDeleteRequest req)
663688
// not found, so return empty list
664689
if (response.StatusCode == HttpStatusCode.NotFound)
665690
{
666-
return (new List<TResponse>(), new DataInfoModel());
691+
return (new List<TResponse>(), new DataInfoModel(), null);
667692
}
668693

669694
// other error
@@ -1197,6 +1222,22 @@ private async Task<HttpResponseMessage> RetryOnUnauthorizedAsync(Func<Task<HttpR
11971222
/// <param name="modId">Modification Id map function.</param>
11981223
/// <param name="input">JSON.NET JToken instance from Data Api Response.</param>
11991224
/// <returns></returns>
1225+
/// <summary>
1226+
/// Extracts script result fields (including pre-request and pre-sort) from a response JToken.
1227+
/// Handles dotted property names that cannot be mapped via attributes.
1228+
/// </summary>
1229+
private static void PopulateScriptResults(ActionResponse target, JToken responseToken)
1230+
{
1231+
if (target == null || responseToken == null) return;
1232+
1233+
target.ScriptError = responseToken["scriptError"]?.ToObject<int>() ?? 0;
1234+
target.ScriptResult = responseToken["scriptResult"]?.ToString();
1235+
target.ScriptErrorPreRequest = responseToken["scriptError.prerequest"]?.ToObject<int>() ?? 0;
1236+
target.ScriptResultPreRequest = responseToken["scriptResult.prerequest"]?.ToString();
1237+
target.ScriptErrorPreSort = responseToken["scriptError.presort"]?.ToObject<int>() ?? 0;
1238+
target.ScriptResultPreSort = responseToken["scriptResult.presort"]?.ToString();
1239+
}
1240+
12001241
private static T ConvertJTokenToInstance<T>(Func<T, int, object> fmId, Func<T, int, object> modId, JToken input) where T : class, new()
12011242
{
12021243
// JToken.ToObject is a helper method that uses JsonSerializer internally
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace FMData.Rest.Responses
2+
{
3+
/// <summary>
4+
/// Delete response instance
5+
/// </summary>
6+
public class DeleteResponse : BaseResponse, IDeleteResponse
7+
{
8+
/// <summary>
9+
/// The response object from the delete request.
10+
/// </summary>
11+
public ActionResponse Response { get; set; }
12+
}
13+
}

src/FMData.Xml/FileMakerXmlClient.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,14 @@ public override async Task<ICreateResponse> SendAsync<T>(ICreateRequest<T> req)
143143
/// <summary>
144144
/// Executes a delete request.
145145
/// </summary>
146-
public override async Task<IResponse> SendAsync(IDeleteRequest req)
146+
public override async Task<IDeleteResponse> SendAsync(IDeleteRequest req)
147147
{
148148
var response = await ExecuteRequestAsync(req).ConfigureAwait(false);
149149

150150
if (response.IsSuccessStatusCode)
151151
{
152152
// process response data return OK
153-
var resp = new CreateResponse
153+
var resp = new DeleteResponse
154154
{
155155
Messages = new List<ResponseMessage> { new ResponseMessage { Code = "", Message = "OK" } }
156156
};
@@ -181,7 +181,7 @@ public override async Task<IEditResponse> SendAsync<T>(IEditRequest<T> req)
181181
}
182182

183183
/// <inheritdoc />
184-
public override async Task<(IEnumerable<TResponse>, DataInfoModel)> SendFindRequestAsync<TResponse, TRequest>(
184+
public override async Task<(IEnumerable<TResponse>, DataInfoModel, ActionResponse)> SendFindRequestAsync<TResponse, TRequest>(
185185
IFindRequest<TRequest> req,
186186
Func<TResponse, int, object> fmId = null,
187187
Func<TResponse, int, object> modId = null)
@@ -286,10 +286,10 @@ public override async Task<IEditResponse> SendAsync<T>(IEditRequest<T> req)
286286
await ProcessContainers(results).ConfigureAwait(false);
287287
}
288288

289-
return (results, new DataInfoModel());
289+
return (results, new DataInfoModel(), null);
290290
}
291291

292-
return (null, new DataInfoModel());
292+
return (null, new DataInfoModel(), null);
293293
}
294294
#endregion
295295

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Collections.Generic;
2+
3+
namespace FMData.Xml.Responses
4+
{
5+
/// <summary>
6+
/// Delete response instance
7+
/// </summary>
8+
public class DeleteResponse : IDeleteResponse
9+
{
10+
/// <summary>
11+
/// The response object from the delete request.
12+
/// </summary>
13+
public ActionResponse Response { get; set; }
14+
15+
/// <summary>
16+
/// The messages from this response.
17+
/// </summary>
18+
public IEnumerable<ResponseMessage> Messages { get; set; }
19+
}
20+
}

src/FMData/FileMakerApiClientBase.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ public Task<IEditResponse> EditAsync(
587587
/// <typeparam name="T">Class with the [Table] attribute specifying the layout to use.</typeparam>
588588
/// <param name="recId">The FileMaker RecordId of the record to delete.</param>
589589
/// <returns></returns>
590-
public Task<IResponse> DeleteAsync<T>(int recId) where T : class, new()
590+
public Task<IDeleteResponse> DeleteAsync<T>(int recId) where T : class, new()
591591
{
592592
var request = GenerateDeleteRequest();
593593
request.Layout = FileMakerApiClientBase.GetLayoutName(new T());
@@ -598,7 +598,7 @@ public Task<IEditResponse> EditAsync(
598598
/// <summary>
599599
/// Delete a record by id and layout.
600600
/// </summary>
601-
public Task<IResponse> DeleteAsync(
601+
public Task<IDeleteResponse> DeleteAsync(
602602
int recId,
603603
string layout)
604604
{
@@ -618,7 +618,7 @@ public Task<IResponse> DeleteAsync(
618618
/// <summary>
619619
/// Send a Delete Record request to the FileMaker API.
620620
/// </summary>
621-
public abstract Task<IResponse> SendAsync(IDeleteRequest req);
621+
public abstract Task<IDeleteResponse> SendAsync(IDeleteRequest req);
622622

623623
/// <summary>
624624
/// Send an Edit Record request to the FileMaker API.
@@ -652,12 +652,12 @@ public virtual async Task<IEnumerable<T>> SendAsync<T>(
652652
Func<T, int, object> fmId,
653653
Func<T, int, object> modId) where T : class, new()
654654
{
655-
var (data, _) = await SendFindRequestAsync(req, fmId, modId).ConfigureAwait(false);
655+
var (data, _, _) = await SendFindRequestAsync(req, fmId, modId).ConfigureAwait(false);
656656
return data;
657657
}
658658

659659
/// <inheritdoc />
660-
public virtual async Task<(IEnumerable<T>, DataInfoModel)> SendAsync<T>(
660+
public virtual async Task<(IEnumerable<T>, DataInfoModel, ActionResponse)> SendAsync<T>(
661661
IFindRequest<T> req,
662662
bool includeDataInfo,
663663
Func<T, int, object> fmId = null,
@@ -667,7 +667,7 @@ public virtual async Task<IEnumerable<T>> SendAsync<T>(
667667
}
668668

669669
/// <inheritdoc />
670-
public abstract Task<(IEnumerable<TResponse>, DataInfoModel)> SendFindRequestAsync<TResponse, TRequest>(
670+
public abstract Task<(IEnumerable<TResponse>, DataInfoModel, ActionResponse)> SendFindRequestAsync<TResponse, TRequest>(
671671
IFindRequest<TRequest> req,
672672
Func<TResponse, int, object> fmId,
673673
Func<TResponse, int, object> modId) where TResponse : class, new();

0 commit comments

Comments
 (0)