Skip to content

Commit 55e40a0

Browse files
Werks
1 parent bd97efe commit 55e40a0

File tree

6 files changed

+1799
-53
lines changed

6 files changed

+1799
-53
lines changed

CLAUDE.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
55
## Code Rules
66

77
- NO DUPLICATION - EVER!!!! REMOVING DUPLICATION is the absolute HIGHEST PRIORITY!!!
8-
- Reduce the AMOUNT of code wherever possible
8+
- YOU ARE NOT ALLOWED TO SKIP TESTS
99
- No throwing exceptions, except for in tests
1010
- FP style code. Pure functions with immutable types.
11-
- 100% test coverage everywhere
12-
- Don't use Git unless I explicitly ask you to
1311
- Keep functions under 20 LOC
1412
- Keep files under 300 LOC
15-
- Use StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers for code quality
16-
- Ccode analysis warnings are always errors
17-
- Nullable reference types are enabled
1813
- NEVER copy files. Only MOVE files
14+
- Don't use Git unless I explicitly ask you to
15+
- Promote code analysis warnings to errors
16+
- Nullable reference types are enabled and MUST be obeyed
1917
- Do not back files up
18+
- Aggressively pursue these aims, even when it means taking more time on a task
2019

2120
## Build, Test, and Development Commands
2221

RestClient.Net.OpenApiGenerator/ExtensionMethodGenerator.cs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public static (string ExtensionMethods, string TypeAliases) GenerateExtensionMet
3232
)
3333
#pragma warning restore IDE0060 // Remove unused parameter
3434
{
35-
var groupedMethods = new Dictionary<string, List<(string PublicMethod, string PrivateDelegate)>>();
35+
var groupedMethods =
36+
new Dictionary<string, List<(string PublicMethod, string PrivateDelegate)>>();
3637
var responseTypes = new HashSet<string>();
3738

3839
foreach (var path in document.Paths)
@@ -172,7 +173,9 @@ private static string GetResourceNameFromPath(string path)
172173
return CodeGenerationHelpers.ToPascalCase(resourceSegment);
173174
}
174175

175-
private static (string PublicMethods, string PrivateDelegates) GenerateGroupedCode(Dictionary<string, List<(string PublicMethod, string PrivateDelegate)>> groupedMethods)
176+
private static (string PublicMethods, string PrivateDelegates) GenerateGroupedCode(
177+
Dictionary<string, List<(string PublicMethod, string PrivateDelegate)>> groupedMethods
178+
)
176179
{
177180
var publicSections = new List<string>();
178181
var allPrivateDelegates = new List<string>();
@@ -193,7 +196,10 @@ private static (string PublicMethods, string PrivateDelegates) GenerateGroupedCo
193196
allPrivateDelegates.AddRange(privateDelegates);
194197
}
195198

196-
var privateDelegatesCode = string.Join("\n\n", allPrivateDelegates.Select(d => CodeGenerationHelpers.Indent(d, 1)));
199+
var privateDelegatesCode = string.Join(
200+
"\n\n",
201+
allPrivateDelegates.Select(d => CodeGenerationHelpers.Indent(d, 1))
202+
);
197203

198204
return (string.Join("\n\n", publicSections), privateDelegatesCode);
199205
}
@@ -342,7 +348,7 @@ string summary
342348
: string.Empty;
343349

344350
var headersExpression = hasHeaderParams
345-
? BuildHeadersDictionaryExpression(headerParams, "param")
351+
? BuildHeadersDictionaryExpression(headerParams, "param", isSingleParam)
346352
: "null";
347353

348354
var buildRequestBody =
@@ -400,11 +406,7 @@ string summary
400406
: string.Empty;
401407

402408
var headersExpression = hasHeaderParams
403-
? (
404-
isSingleParam && headerParams.Count == 1
405-
? BuildHeadersDictionaryExpression(headerParams, "param")
406-
: BuildHeadersDictionaryExpression(headerParams, "param")
407-
)
409+
? BuildHeadersDictionaryExpression(headerParams, "param", isSingleParam)
408410
: "null";
409411

410412
var sanitizedPath = CodeGenerationHelpers.SanitizePathParameters(
@@ -513,7 +515,7 @@ string summary
513515
: string.Empty;
514516

515517
var headersExpression = hasHeaderParams
516-
? BuildHeadersDictionaryExpression(headerParams, "param")
518+
? BuildHeadersDictionaryExpression(headerParams, "param", false)
517519
: "null";
518520

519521
var buildRequestBody =
@@ -526,11 +528,11 @@ string summary
526528
: string.Join(", ", pathParams.Select(p => $"{p.Type} {p.Name}"))
527529
+ $", {bodyType} body";
528530

529-
var publicMethodInvocation = hasNonPathNonBodyParams
530-
? $"({string.Join(", ", parameters.Select(p => p.Name))}, body)"
531-
: isSinglePathParam
532-
? $"({pathParamsNames}, body)"
533-
: $"(({pathParamsNames}), body)";
531+
var publicMethodInvocation =
532+
hasNonPathNonBodyParams
533+
? $"({string.Join(", ", parameters.Select(p => p.Name))}, body)"
534+
: isSinglePathParam ? $"({pathParamsNames}, body)"
535+
: $"(({pathParamsNames}), body)";
534536

535537
return BuildMethod(
536538
methodName,
@@ -706,7 +708,8 @@ string summary
706708
: $"{publicParams},";
707709

708710
// Derive delegate type name: CreatePost → PostAsync, CreateGet → GetAsync, etc.
709-
var delegateType = createMethod.Replace("Create", string.Empty, StringComparison.Ordinal) + "Async";
711+
var delegateType =
712+
createMethod.Replace("Create", string.Empty, StringComparison.Ordinal) + "Async";
710713

711714
var privateDelegate = $$"""
712715
private static {{delegateType}}<{{resultResponseType}}, string, {{paramType}}> {{privateFunctionName}} { get; } =
@@ -732,20 +735,23 @@ string summary
732735

733736
private static string BuildHeadersDictionaryExpression(
734737
List<ParameterInfo> headerParams,
735-
string paramPrefix = "param"
738+
string paramPrefix = "param",
739+
bool isSingleOverallParam = false
736740
)
737741
{
738742
if (headerParams.Count == 0)
739743
{
740744
return "null";
741745
}
742746

743-
if (headerParams.Count == 1)
747+
// Only use param.ToString() directly if we have a single overall parameter that IS the header
748+
if (headerParams.Count == 1 && isSingleOverallParam)
744749
{
745750
var h = headerParams[0];
746751
return $"new Dictionary<string, string> {{ [\"{h.OriginalName}\"] = {paramPrefix}.ToString() ?? string.Empty }}";
747752
}
748753

754+
// Otherwise, we have a tuple and need to access param.{headerName}
749755
var entries = string.Join(
750756
", ",
751757
headerParams.Select(h =>

Samples/NucliaDbClient.Tests/NucliaDbApiTests.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,33 @@ public NucliaDbApiTests()
1818
_httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
1919

2020
// Use environment variable or default to the KB created by Docker container
21-
_kbid = Environment.GetEnvironmentVariable("NUCLIA_KBID") ?? "2edd5a30-8e28-4185-a0be-629971a9784c";
21+
_kbid =
22+
Environment.GetEnvironmentVariable("NUCLIA_KBID")
23+
?? "2edd5a30-8e28-4185-a0be-629971a9784c";
2224
}
2325

24-
[SkippableFact]
25-
public async Task GetKnowledgeBox_ReturnsValidData()
26+
private static HttpClient ConfigureAuthentication(HttpClient httpClient)
2627
{
27-
Skip.If(true, "Requires X-NUCLIADB-ROLES header which is not in the OpenAPI spec");
28+
// NucliaDB requires either service account token or roles header
29+
// For local testing, we use the X-NUCLIADB-ROLES header to specify access level
30+
var serviceAccount = Environment.GetEnvironmentVariable("NUCLIA_SERVICE_ACCOUNT");
31+
if (!string.IsNullOrEmpty(serviceAccount))
32+
{
33+
httpClient.DefaultRequestHeaders.Add("X-NUCLIA-SERVICEACCOUNT", $"Bearer {serviceAccount}");
34+
}
35+
else
36+
{
37+
// For local Docker instance without authentication, use roles header
38+
httpClient.DefaultRequestHeaders.Add("X-NUCLIADB-ROLES", "READER");
39+
}
40+
return httpClient;
41+
}
2842

43+
[Fact]
44+
public async Task GetKnowledgeBox_ReturnsValidData()
45+
{
2946
// Arrange
30-
var httpClient = _httpClientFactory.CreateClient();
47+
var httpClient = ConfigureAuthentication(_httpClientFactory.CreateClient());
3148

3249
// Act
3350
var result = await httpClient.GetKbKbKbidGet(_kbid!).ConfigureAwait(false);
@@ -53,10 +70,8 @@ public async Task GetKnowledgeBox_ReturnsValidData()
5370
[SkippableFact]
5471
public async Task ListResources_ReturnsResourceList()
5572
{
56-
Skip.If(true, "Requires X-NUCLIADB-ROLES header which is not in the OpenAPI spec");
57-
5873
// Arrange
59-
var httpClient = _httpClientFactory.CreateClient();
74+
var httpClient = ConfigureAuthentication(_httpClientFactory.CreateClient());
6075

6176
// Act
6277
var result = await httpClient

Samples/NucliaDbClient/Generated/NucliaDBApiExtensions.g.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ public static Task<Result<object, HttpError<string>>> LearningConfigurationSchem
12291229
private static PutAsync<ResourceFieldAdded, string, (string kbid, string rid, string fieldId, bool xSkipStore, FileField Body)> _addResourceFieldFileRidPrefixKbKbidResourceRidFileFieldIdPut { get; } =
12301230
RestClient.Net.HttpClientFactoryExtensions.CreatePut<ResourceFieldAdded, string, (string kbid, string rid, string fieldId, bool xSkipStore, FileField Body)>(
12311231
url: BaseUrl,
1232-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/file/{param.fieldId}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-skip-store"] = param.ToString() ?? string.Empty }),
1232+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/file/{param.fieldId}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-skip-store"] = param.xSkipStore.ToString() ?? string.Empty }),
12331233
deserializeSuccess: DeserializeJson<ResourceFieldAdded>,
12341234
deserializeError: DeserializeError
12351235
);
@@ -1277,23 +1277,23 @@ public static Task<Result<object, HttpError<string>>> LearningConfigurationSchem
12771277
private static PostAsync<ResourceUpdated, string, (string kbid, string rid, bool resetTitle, string xNucliadbUser, object Body)> _reprocessResourceRidPrefixKbKbidResourceRidReprocessPost { get; } =
12781278
RestClient.Net.HttpClientFactoryExtensions.CreatePost<ResourceUpdated, string, (string kbid, string rid, bool resetTitle, string xNucliadbUser, object Body)>(
12791279
url: BaseUrl,
1280-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/reprocess?reset_title={param.resetTitle}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.ToString() ?? string.Empty }),
1280+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/reprocess?reset_title={param.resetTitle}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.xNucliadbUser.ToString() ?? string.Empty }),
12811281
deserializeSuccess: DeserializeJson<ResourceUpdated>,
12821282
deserializeError: DeserializeError
12831283
);
12841284

12851285
private static PostAsync<ResourceAgentsResponse, string, (string kbid, string rid, string xNucliadbUser, ResourceAgentsRequest Body)> _runAgentsByUuidKbKbidResourceRidRunAgentsPost { get; } =
12861286
RestClient.Net.HttpClientFactoryExtensions.CreatePost<ResourceAgentsResponse, string, (string kbid, string rid, string xNucliadbUser, ResourceAgentsRequest Body)>(
12871287
url: BaseUrl,
1288-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/run-agents"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.ToString() ?? string.Empty }),
1288+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/run-agents"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.xNucliadbUser.ToString() ?? string.Empty }),
12891289
deserializeSuccess: DeserializeJson<ResourceAgentsResponse>,
12901290
deserializeError: DeserializeError
12911291
);
12921292

12931293
private static GetAsync<ResourceSearchResults, string, (string kbid, string rid, string query, object filterExpression, List<string> fields, List<string> filters, List<string> faceted, object sortField, SortOrder sortOrder, object topK, object rangeCreationStart, object rangeCreationEnd, object rangeModificationStart, object rangeModificationEnd, bool highlight, bool debug, NucliaDBClientType xNdbClient)> _resourceSearchKbKbidResourceRidSearchGet { get; } =
12941294
RestClient.Net.HttpClientFactoryExtensions.CreateGet<ResourceSearchResults, string, (string kbid, string rid, string query, object filterExpression, List<string> fields, List<string> filters, List<string> faceted, object sortField, SortOrder sortOrder, object topK, object rangeCreationStart, object rangeCreationEnd, object rangeModificationStart, object rangeModificationEnd, bool highlight, bool debug, NucliaDBClientType xNdbClient)>(
12951295
url: BaseUrl,
1296-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/search?query={param.query}&filter_expression={param.filterExpression}&fields={param.fields}&filters={param.filters}&faceted={param.faceted}&sort_field={param.sortField}&sort_order={param.sortOrder}&top_k={param.topK}&range_creation_start={param.rangeCreationStart}&range_creation_end={param.rangeCreationEnd}&range_modification_start={param.rangeModificationStart}&range_modification_end={param.rangeModificationEnd}&highlight={param.highlight}&debug={param.debug}"), null, new Dictionary<string, string> { ["x-ndb-client"] = param.ToString() ?? string.Empty }),
1296+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/resource/{param.rid}/search?query={param.query}&filter_expression={param.filterExpression}&fields={param.fields}&filters={param.filters}&faceted={param.faceted}&sort_field={param.sortField}&sort_order={param.sortOrder}&top_k={param.topK}&range_creation_start={param.rangeCreationStart}&range_creation_end={param.rangeCreationEnd}&range_modification_start={param.rangeModificationStart}&range_modification_end={param.rangeModificationEnd}&highlight={param.highlight}&debug={param.debug}"), null, new Dictionary<string, string> { ["x-ndb-client"] = param.xNdbClient.ToString() ?? string.Empty }),
12971297
deserializeSuccess: DeserializeJson<ResourceSearchResults>,
12981298
deserializeError: DeserializeError
12991299
);
@@ -1461,7 +1461,7 @@ public static Task<Result<object, HttpError<string>>> LearningConfigurationSchem
14611461
private static PutAsync<ResourceFieldAdded, string, (string kbid, string rslug, string fieldId, bool xSkipStore, FileField Body)> _addResourceFieldFileRslugPrefixKbKbidSlugRslugFileFieldIdPut { get; } =
14621462
RestClient.Net.HttpClientFactoryExtensions.CreatePut<ResourceFieldAdded, string, (string kbid, string rslug, string fieldId, bool xSkipStore, FileField Body)>(
14631463
url: BaseUrl,
1464-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/slug/{param.rslug}/file/{param.fieldId}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-skip-store"] = param.ToString() ?? string.Empty }),
1464+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/slug/{param.rslug}/file/{param.fieldId}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-skip-store"] = param.xSkipStore.ToString() ?? string.Empty }),
14651465
deserializeSuccess: DeserializeJson<ResourceFieldAdded>,
14661466
deserializeError: DeserializeError
14671467
);
@@ -1525,7 +1525,7 @@ public static Task<Result<object, HttpError<string>>> LearningConfigurationSchem
15251525
private static PostAsync<ResourceUpdated, string, (string kbid, string rslug, bool resetTitle, string xNucliadbUser, object Body)> _reprocessResourceRslugPrefixKbKbidSlugRslugReprocessPost { get; } =
15261526
RestClient.Net.HttpClientFactoryExtensions.CreatePost<ResourceUpdated, string, (string kbid, string rslug, bool resetTitle, string xNucliadbUser, object Body)>(
15271527
url: BaseUrl,
1528-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/slug/{param.rslug}/reprocess?reset_title={param.resetTitle}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.ToString() ?? string.Empty }),
1528+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/slug/{param.rslug}/reprocess?reset_title={param.resetTitle}"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.xNucliadbUser.ToString() ?? string.Empty }),
15291529
deserializeSuccess: DeserializeJson<ResourceUpdated>,
15301530
deserializeError: DeserializeError
15311531
);
@@ -1573,7 +1573,7 @@ public static Task<Result<object, HttpError<string>>> LearningConfigurationSchem
15731573
private static PostAsync<ResourceAgentsResponse, string, (string kbid, string slug, string xNucliadbUser, ResourceAgentsRequest Body)> _runAgentsBySlugKbKbidSlugSlugRunAgentsPost { get; } =
15741574
RestClient.Net.HttpClientFactoryExtensions.CreatePost<ResourceAgentsResponse, string, (string kbid, string slug, string xNucliadbUser, ResourceAgentsRequest Body)>(
15751575
url: BaseUrl,
1576-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/slug/{param.slug}/run-agents"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.ToString() ?? string.Empty }),
1576+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/slug/{param.slug}/run-agents"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-nucliadb-user"] = param.xNucliadbUser.ToString() ?? string.Empty }),
15771577
deserializeSuccess: DeserializeJson<ResourceAgentsResponse>,
15781578
deserializeError: DeserializeError
15791579
);
@@ -1621,7 +1621,7 @@ public static Task<Result<object, HttpError<string>>> LearningConfigurationSchem
16211621
private static PostAsync<SummarizedResponse, string, (string kbid, bool xShowConsumption, SummarizeRequest Body)> _summarizeEndpointKbKbidSummarizePost { get; } =
16221622
RestClient.Net.HttpClientFactoryExtensions.CreatePost<SummarizedResponse, string, (string kbid, bool xShowConsumption, SummarizeRequest Body)>(
16231623
url: BaseUrl,
1624-
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/summarize"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-show-consumption"] = param.ToString() ?? string.Empty }),
1624+
buildRequest: static param => new HttpRequestParts(new RelativeUrl($"/api/v1/kb/{param.kbid}/summarize"), CreateJsonContent(param.Body), new Dictionary<string, string> { ["x-show-consumption"] = param.xShowConsumption.ToString() ?? string.Empty }),
16251625
deserializeSuccess: DeserializeJson<SummarizedResponse>,
16261626
deserializeError: DeserializeError
16271627
);

0 commit comments

Comments
 (0)