Question: Async ScriptObject?
🙋♂️This is more of a question than a real issue
I've used Scriban in the past, but on a basic level. Lately I've been playing around with an idea that would require to make HTTP request during evaluation to resolve corresponding data from an API, in order to avoid an expensive large query/payload upfront.
As an example, the following expression should retrieve data from a Projects database, retrieving async data from it and make an additional requests to get a manager object from a Users database.
{{ project = projects('PROJECT_ID') }}
{{ status = project.status}}
{{ manager = users(project.manager.id) }}
// I assume this could be another construct to get the manager
// {{ manager = project.manager.id | users }}
# {{ project.name }}
| Id | Manager | Status |
| ---------------- | ------------------ | ------------ |
| {{ project.id }} | {{ manager.name }} | {{ status }} |
I've sort of managed to do this by implementing a ScriptObject that imports an async method to itself, so that can execute with an identifier or any other argument as input returning a Task<object>. Note that ProjectScriptObject also inherits from ScriptObject so it has similar logic to retrieve additional resources from another database based on a project's data (ie project/{project_id}/files, project/{project_id}/deliverables, etc)
internal class APIScriptObject : ScriptObject
{
private readonly HttpClient _client;
public APIScriptObject(HttpClient client): base(StringComparer.OrdinalIgnoreCase)
{
_client = client;
this.Import(nameof(Projects), Projects);
this.Import(nameof(Users), Users);
}
public async Task<object> Projects(string identifier)
{
ProjectsScriptObject project= await _client.GetAsync(....);
return project;
}
public async Task<object> Users(string identifier)
{
UserScriptObject user= await _client.GetAsync(....);
return user;
}
}
The above seems to work ok, with the only limitation (afaik) being the return type must be Task<object> as otherwise it throws an exception with message Cannot implicitly convert type 'void' to 'object' here if for example I try to return Task<ProjectScriptObject>.
I think I can work with this approach for the time being, just wondering if there would be a more robust and reliable way using any library's ootb mechanism. I've looked at visitor pattern but don't think it really fits on this use case (unless I've misunderstood how to use it).
The above seems to work ok, with the only limitation (afaik) being the return type must be
Task<object>as otherwise it throws an exception with message Cannot implicitly convert type 'void' to 'object' here if for example I try to returnTask<ProjectScriptObject>.
I think originally the code was using Task<object> for the return type, but using ValueTask<object> might not work well with dynamic. In that case, I believe a fix could be to cast first to Task<object> before changing into a value task.
I can live with method signatures returning Task<object> instead of strongly typed :)
Regarding the approach mentioned, I was hoping there was a way to parse the template to get this custom objects so I could only prefetch the needed data from the API.