diff --git a/DragonFruit.Data.Roslyn.Tests/ApiRequestTemplateTests.cs b/DragonFruit.Data.Roslyn.Tests/ApiRequestTemplateTests.cs
deleted file mode 100644
index cb4c57e..0000000
--- a/DragonFruit.Data.Roslyn.Tests/ApiRequestTemplateTests.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// DragonFruit.Data Copyright DragonFruit Network
-// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details
-
-using System.IO;
-using System.Threading.Tasks;
-using Scriban;
-using Xunit;
-
-namespace DragonFruit.Data.Roslyn.Tests
-{
- public class ApiRequestTemplateTests
- {
- [Fact]
- public async Task TestTemplateParse()
- {
- var assembly = typeof(ApiRequestSourceGenerator).Assembly;
- using var template = assembly.GetManifestResourceStream(ApiRequestSourceGenerator.TemplateName);
-
- Assert.NotNull(template);
-
- using var templateReader = new StreamReader(template);
- var templateText = await templateReader.ReadToEndAsync();
-
- Assert.True(templateText.Length > 0);
-
- var templateAst = Template.ParseLiquid(templateText);
-
- Assert.False(templateAst.HasErrors);
- }
- }
-}
diff --git a/DragonFruit.Data.Roslyn.Tests/DragonFruit.Data.Roslyn.Tests.csproj b/DragonFruit.Data.Roslyn.Tests/DragonFruit.Data.Roslyn.Tests.csproj
index 4fd1b49..bd38b82 100644
--- a/DragonFruit.Data.Roslyn.Tests/DragonFruit.Data.Roslyn.Tests.csproj
+++ b/DragonFruit.Data.Roslyn.Tests/DragonFruit.Data.Roslyn.Tests.csproj
@@ -6,12 +6,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/DragonFruit.Data.Roslyn/ApiRequestSourceBuilder.cs b/DragonFruit.Data.Roslyn/ApiRequestSourceBuilder.cs
new file mode 100644
index 0000000..6d67aed
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/ApiRequestSourceBuilder.cs
@@ -0,0 +1,292 @@
+// DragonFruit.Data Copyright DragonFruit Network
+// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using DragonFruit.Data.Requests;
+using DragonFruit.Data.Roslyn.Entities;
+using DragonFruit.Data.Roslyn.Enums;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+using StrawberryShake.CodeGeneration;
+using StrawberryShake.CodeGeneration.CSharp.Builders;
+
+namespace DragonFruit.Data.Roslyn;
+
+internal static class ApiRequestSourceBuilder
+{
+ private static readonly SyntaxTriviaList AutoGeneratedComment = SyntaxFactory.TriviaList(
+ SyntaxFactory.Comment("// "),
+ SyntaxFactory.CarriageReturnLineFeed,
+ SyntaxFactory.CarriageReturnLineFeed);
+
+ private static readonly UsingDirectiveSyntax[] DefaultUsingStatements =
+ [
+ SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")),
+ SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Text")),
+ SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Net.Http")),
+ SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("DragonFruit.Data")),
+ SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("DragonFruit.Data.Requests"))
+ ];
+
+ public static SourceText Build(INamedTypeSymbol classSymbol, RequestSymbolMetadata metadata, RequestBodyType requestBodyType, bool requireNewKeyword = false)
+ {
+ // classes are partial by default
+ var classBuilder = new ClassBuilder()
+ .SetName(classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat))
+ .AddImplements("global::DragonFruit.Data.Requests.IRequestBuilder");
+
+ var serializerMethodParamBuilder = new ParameterBuilder()
+ .SetType("global::DragonFruit.Data.Serializers.SerializerResolver")
+ .SetName("serializerResolver");
+
+ var methodBuilder = new MethodBuilder()
+ .SetName("BuildRequest")
+ .SetReturnType("global::System.Net.Http.HttpRequestMessage")
+ .SetAccessModifier(AccessModifier.Public)
+ .AddParameter(serializerMethodParamBuilder);
+
+ // create a new UriBuilder called uriBuilder pulling in the uri from this.RequestPath
+ var methodBodyBuilder = new CodeBlockBuilder();
+ var buildQueryString = metadata.Properties[ParameterType.Query].Any();
+
+ // process queries
+ if (buildQueryString)
+ {
+ methodBodyBuilder.AddCode("var uriBuilder = new global::System.Text.StringBuilder(this.RequestPath);").AddEmptyLine();
+ methodBodyBuilder.AddCode("var queryBuilder = new global::System.Text.StringBuilder();").AddEmptyLine();
+
+ WriteUriQueryBuilder(methodBodyBuilder, metadata.Properties[ParameterType.Query].OfType(), "queryBuilder");
+
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition("queryBuilder.Length > 0")
+ .AddCode("queryBuilder.Length--;")
+ .AddCode("uriBuilder.Append(\"?\").Append(queryBuilder);"));
+ methodBodyBuilder.AddEmptyLine();
+ methodBodyBuilder.AddEmptyLine();
+ }
+
+ // create request body (w/ shortcut to reduce allocations when no query is generated)
+ var uriAccessor = buildQueryString ? "uriBuilder.ToString()" : "this.RequestPath";
+ methodBodyBuilder.AddCode($"var request = new global::System.Net.Http.HttpRequestMessage(this.RequestMethod, {uriAccessor});").AddEmptyLine();
+
+ // process body content
+ switch (requestBodyType)
+ {
+ case RequestBodyType.CustomBodyDirect:
+ methodBodyBuilder.AddCode($"request.Content = {metadata.BodyProperty.Accessor};");
+ break;
+
+ case RequestBodyType.CustomBodySerialized:
+ methodBodyBuilder.AddCode($"var requestContentData = {metadata.BodyProperty.Accessor};");
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition("requestContentData != null")
+ .AddCode("request.Content = serializerResolver.Resolve(requestContentData.GetType(), global::DragonFruit.Data.Serializers.DataDirection.Out).Serialize(requestContentData);"));
+ break;
+
+ case RequestBodyType.FormUriEncoded:
+ methodBodyBuilder.AddCode("var formContentBuilder = new global::System.Text.StringBuilder();").AddEmptyLine();
+ WriteUriQueryBuilder(methodBodyBuilder, metadata.Properties[ParameterType.Form].OfType(), "formContentBuilder");
+
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition("formContentBuilder.Length > 0").AddCode("formContentBuilder.Length--;"));
+ methodBodyBuilder.AddCode("request.Content = new global::System.Net.Http.StringContent(formContentBuilder.ToString(), global::System.Text.Encoding.UTF8, \"application/x-www-form-urlencoded\");");
+ break;
+
+ case RequestBodyType.FormMultipart:
+ methodBodyBuilder.AddCode("var multipartContent = new global::System.Net.Http.MultipartFormDataContent();");
+
+ int counter = 0;
+
+ foreach (var symbol in metadata.Properties[ParameterType.Form].OfType())
+ {
+ string variableName;
+
+ switch (symbol.Type)
+ {
+ case RequestSymbolType.ByteArray:
+ {
+ variableName = $"ba{++counter}";
+ methodBodyBuilder.AddCode($"var {variableName} = {symbol.Accessor};");
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition($"{variableName} != null").AddCode($"multipartContent.Add(new global::System.Net.Http.ByteArrayContent({variableName}), \"{symbol.ParameterName}\");"));
+ break;
+ }
+
+ case RequestSymbolType.Stream:
+ {
+ variableName = $"st{++counter}";
+ methodBodyBuilder.AddCode($"var {variableName} = {symbol.Accessor};");
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition($"{variableName} != null").AddCode($"multipartContent.Add(new global::System.Net.Http.StreamContent({variableName}), \"{symbol.ParameterName}\");"));
+ break;
+ }
+
+ case RequestSymbolType.Enum when symbol is EnumSymbolMetadata enumSymbol:
+ {
+ var line = $"global::System.Net.Http.StringContent(global::DragonFruit.Data.Converters.EnumConverter.GetEnumValue({symbol.Accessor}, global::DragonFruit.Data.Requests.EnumOption.{enumSymbol.EnumOption})), \"{symbol.ParameterName};\");";
+
+ if (symbol.Nullable)
+ {
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition($"{symbol.Accessor} != null").AddCode(line));
+ }
+ else
+ {
+ methodBodyBuilder.AddCode(line);
+ }
+
+ break;
+ }
+
+ case RequestSymbolType.Enumerable when symbol is EnumerableSymbolMetadata enumerableSymbol:
+ {
+ if (enumerableSymbol.IsByteArray)
+ {
+ goto case RequestSymbolType.ByteArray;
+ }
+
+ var enumerableBlock = new ForEachBuilder()
+ .SetLoopHeader($"var item in global::DragonFruit.Data.Converters.EnumerableConverter.GetPairs({symbol.Accessor}, global::DragonFruit.Data.Requests.EnumerableOption.{enumerableSymbol.EnumerableOption})")
+ .AddCode("multipartContent.Add(new global::System.Net.Http.StringContent(item.Value), item.Key);");
+
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition($"{symbol.Accessor} != null").AddCode(enumerableBlock));
+ break;
+ }
+
+ case RequestSymbolType.KeyValuePair:
+ {
+ var keyValuePairBlock = new ForEachBuilder()
+ .SetLoopHeader($"var item in (global::System.Collections.Generic.IEnumerable>){symbol.Accessor}")
+ .AddCode("multipartContent.Add(new global::System.Net.Http.StringContent(item.Value), item.Key);");
+
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition($"{symbol.Accessor} != null").AddCode(keyValuePairBlock));
+ break;
+ }
+
+ default:
+ {
+ var line = $"multipartContent.Add(new global::System.Net.Http.StringContent({symbol.Accessor}), \"{symbol.ParameterName}\");";
+
+ if (symbol.Nullable)
+ {
+ methodBodyBuilder.AddCode(new IfBuilder().SetCondition($"{symbol.Accessor} != null").AddCode(line));
+ }
+ else
+ {
+ methodBodyBuilder.AddCode(line);
+ }
+
+ break;
+ }
+ }
+ }
+
+ methodBodyBuilder.AddCode("request.Content = multipartContent;");
+ break;
+ }
+
+ // process headers
+ if (metadata.Properties[ParameterType.Header].Any())
+ {
+ methodBodyBuilder.AddEmptyLine().AddEmptyLine();
+
+ foreach (var symbol in metadata.Properties[ParameterType.Header].OfType())
+ {
+ var headerHandler = new IfBuilder().SetCondition($"{symbol.Accessor} != null");
+
+ switch (symbol)
+ {
+ case EnumerableSymbolMetadata enumerableSymbol:
+ headerHandler.AddCode(new ForEachBuilder().SetLoopHeader($"var kvp in global::DragonFruit.Data.Converters.EnumerableConverter.GetPairs({symbol.Accessor}, global::DragonFruit.Data.Requests.EnumerableOption.{enumerableSymbol.EnumerableOption}, \"{symbol.ParameterName}\", \"{enumerableSymbol.Separator}\")")
+ .AddCode("request.Headers.Add(kvp.Key, kvp.Value);"));
+ break;
+
+ case KeyValuePairSymbolMetadata:
+ headerHandler.AddCode(new ForEachBuilder().SetLoopHeader($"var kvp in (global::System.Collections.Generic.IEnumerable>){symbol.Accessor}")
+ .AddCode("request.Headers.Add(kvp.Key, kvp.Value);"));
+ break;
+
+ // enums are the only thing here that may/may not be nullable
+ case EnumSymbolMetadata enumSymbol:
+ var enumBlock = $"request.Headers.Add(\"{symbol.ParameterName}\", global::DragonFruit.Data.Converters.EnumConverter.GetEnumValue({symbol.Accessor}, global::DragonFruit.Data.Requests.EnumOption.{enumSymbol.EnumOption}));";
+
+ if (symbol.Nullable)
+ {
+ headerHandler.AddCode(enumBlock);
+ break;
+ }
+
+ methodBodyBuilder.AddCode(enumBlock);
+ continue;
+
+ default:
+ var block = $"request.Headers.Add(\"{symbol.ParameterName}\", {symbol.Accessor}.ToString());";
+
+ if (symbol.Nullable)
+ {
+ headerHandler.AddCode(block);
+ break;
+ }
+
+ methodBodyBuilder.AddCode(block);
+ break;
+ }
+
+ methodBodyBuilder.AddCode(headerHandler).AddEmptyLine();
+ }
+ }
+
+ methodBodyBuilder.AddCode("return request;");
+ methodBuilder.AddCode(methodBodyBuilder);
+ classBuilder.AddMethod(methodBuilder);
+
+ var code = new StringBuilder(5000);
+
+ using (var writer = new CodeWriter(code))
+ {
+ classBuilder.Build(writer);
+ }
+
+ // add class with generated code annotation added to method (not class)
+ var tree = CSharpSyntaxTree.ParseText(SourceText.From(code.ToString()));
+ var classNode = tree.GetRoot().DescendantNodes().OfType().Single();
+ var ns = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(classSymbol.ContainingNamespace.ToDisplayString()));
+
+ if (requireNewKeyword)
+ {
+ var method = classNode.DescendantNodes().OfType().Single();
+ var newModifiers = method.Modifiers.Add(SyntaxFactory.Token(SyntaxKind.NewKeyword));
+
+ classNode = classNode.ReplaceNode(method, method.WithModifiers(newModifiers));
+ }
+
+ return SyntaxFactory.CompilationUnit()
+ .AddUsings(DefaultUsingStatements)
+ .AddMembers(ns.AddMembers(classNode))
+ .NormalizeWhitespace()
+ .WithLeadingTrivia(AutoGeneratedComment) // needs to go at the bottom to ensure it's put at the top of the file
+ .GetText(Encoding.UTF8);
+ }
+
+ private static void WriteUriQueryBuilder(CodeBlockBuilder builder, IEnumerable symbols, string builderName)
+ {
+ foreach (var querySymbol in symbols)
+ {
+ var codeBlock = querySymbol switch
+ {
+ EnumerableSymbolMetadata enumerableSymbol => $"global::DragonFruit.Data.Converters.EnumerableConverter.WriteEnumerable({builderName}, {querySymbol.Accessor}, global::DragonFruit.Data.Requests.EnumerableOption.{enumerableSymbol.EnumerableOption}, \"{querySymbol.ParameterName}\", \"{enumerableSymbol.Separator}\");",
+ EnumSymbolMetadata enumSymbol => $"global::DragonFruit.Data.Converters.EnumConverter.WriteEnum({builderName}, {querySymbol.Accessor}, global::DragonFruit.Data.Requests.EnumOption.{enumSymbol.EnumOption}, \"{querySymbol.ParameterName}\");",
+ KeyValuePairSymbolMetadata => $"global::DragonFruit.Data.Converters.KeyValuePairConverter.WriteKeyValuePairs({builderName}, {querySymbol.Accessor});",
+
+ _ => $"{builderName}.AppendFormat(\"{{0}}={{1}}&\", \"{querySymbol.ParameterName}\", global::System.Uri.EscapeDataString({querySymbol.Accessor}.ToString()));"
+ };
+
+ if (querySymbol.Nullable)
+ {
+ builder.AddCode(new IfBuilder().SetCondition($"{querySymbol.Accessor} != null").AddCode(codeBlock)).AddEmptyLine();
+ }
+ else
+ {
+ builder.AddCode(codeBlock).AddEmptyLine();
+ }
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/ApiRequestSourceGenerator.cs b/DragonFruit.Data.Roslyn/ApiRequestSourceGenerator.cs
index 7baba03..643e40a 100644
--- a/DragonFruit.Data.Roslyn/ApiRequestSourceGenerator.cs
+++ b/DragonFruit.Data.Roslyn/ApiRequestSourceGenerator.cs
@@ -8,7 +8,6 @@
using System.IO;
using System.Linq;
using System.Net.Http;
-using System.Text;
using DragonFruit.Data.Converters;
using DragonFruit.Data.Requests;
using DragonFruit.Data.Roslyn.Entities;
@@ -16,16 +15,12 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
-using Scriban;
namespace DragonFruit.Data.Roslyn
{
[Generator(LanguageNames.CSharp)]
public class ApiRequestSourceGenerator : IIncrementalGenerator
{
- public static readonly string TemplateName = "DragonFruit.Data.Roslyn.Templates.ApiRequest.liquid";
-
private static readonly HashSet SupportedCollectionTypes =
[
..new[]
@@ -40,25 +35,8 @@ public class ApiRequestSourceGenerator : IIncrementalGenerator
}
];
- private Template _partialRequestTemplate;
-
- static ApiRequestSourceGenerator()
- {
- ExternalDependencyLoader.RegisterDependencyLoader();
- }
-
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- using var templateStream = typeof(ApiRequestSourceGenerator).Assembly.GetManifestResourceStream(TemplateName);
-
- if (templateStream == null)
- {
- throw new NullReferenceException("Could not find template");
- }
-
- using var reader = new StreamReader(templateStream);
- _partialRequestTemplate = Template.ParseLiquid(reader.ReadToEnd());
-
var apiRequestDerivedClasses = context.SyntaxProvider.CreateSyntaxProvider(
predicate: (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax classDecl && classDecl.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)),
transform: (generatorSyntaxContext, _) => GetSemanticTarget(generatorSyntaxContext));
@@ -81,7 +59,6 @@ private void Execute(Compilation compilation, ImmutableArray");
var metadata = GetRequestSymbolMetadata(compilation, classSymbol);
// check if body is derived from httpcontent
@@ -97,39 +74,8 @@ private void Execute(Compilation compilation, ImmutableArray x.Name)));
- genericNameBuilder.Append(">");
-
- className = genericNameBuilder.ToString();
- }
-
- // create template info object
- var parameterInfo = new
- {
- ClassName = className,
- Namespace = classSymbol.ContainingNamespace.ToDisplayString(),
-
- RequireNewKeyword = WillHideOtherMembers(classSymbol, compilation.GetTypeByMetadataName(typeof(ApiRequest).FullName)),
-
- RequestBodyType = requestBodyType,
- RequestBodySymbol = metadata.Properties[ParameterType.Form].Any() ? null : metadata.BodyProperty, // prefer using forms over body - this to match reflection-based behaviour
-
- QueryParameters = metadata.Properties[ParameterType.Query],
- HeaderParameters = metadata.Properties[ParameterType.Header],
- FormBodyParameters = metadata.Properties[ParameterType.Form],
- };
-
- sourceBuilder.Append("\n\n");
- sourceBuilder.Append(_partialRequestTemplate.Render(parameterInfo));
- context.AddSource($"{classSymbol.Name}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
+ var requireNewKeyword = WillHideOtherMembers(classSymbol, compilation.GetTypeByMetadataName(typeof(ApiRequest).FullName));
+ context.AddSource($"{classSymbol.Name}.dragonfruit.g.cs", ApiRequestSourceBuilder.Build(classSymbol, metadata, requestBodyType, requireNewKeyword));
}
}
@@ -286,12 +232,12 @@ private static RequestSymbolMetadata GetRequestSymbolMetadata(Compilation compil
// string (IEnumerable)
else if (returnType.SpecialType == SpecialType.System_String)
{
- symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName);
+ symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName);
}
// Stream
else if (streamTypeSymbol.Equals(returnType, SymbolEqualityComparer.Default) || DerivesFrom(returnType, streamTypeSymbol))
{
- symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.Stream);
+ symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.Stream);
}
else
{
@@ -300,7 +246,7 @@ private static RequestSymbolMetadata GetRequestSymbolMetadata(Compilation compil
{
// byte[]
case true when returnType is IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_Byte }:
- symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.ByteArray);
+ symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName, RequestSymbolType.ByteArray);
break;
// IEnumerable>
@@ -322,7 +268,7 @@ private static RequestSymbolMetadata GetRequestSymbolMetadata(Compilation compil
}
default:
- symbolMetadata = new PropertySymbolMetadata(candidate, returnType, parameterName);
+ symbolMetadata = new ParameterSymbolMetadata(candidate, returnType, parameterName);
break;
}
}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/.editorconfig b/DragonFruit.Data.Roslyn/CodeGeneration/.editorconfig
new file mode 100644
index 0000000..5a284b1
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/.editorconfig
@@ -0,0 +1,5 @@
+root = true
+
+[*.cs]
+generated_code = true
+dotnet_analyzer_diagnostic.severity = none
\ No newline at end of file
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/AccessModifier.cs b/DragonFruit.Data.Roslyn/CodeGeneration/AccessModifier.cs
new file mode 100644
index 0000000..2f23f59
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/AccessModifier.cs
@@ -0,0 +1,9 @@
+namespace StrawberryShake.CodeGeneration;
+
+public enum AccessModifier
+{
+ Public = 0,
+ Internal = 1,
+ Protected = 2,
+ Private = 3,
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/AbstractTypeBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/AbstractTypeBuilder.cs
new file mode 100644
index 0000000..812f102
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/AbstractTypeBuilder.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public abstract class AbstractTypeBuilder : ITypeBuilder
+{
+ protected List Properties { get; } = [];
+
+ protected string? Name { get; private set; }
+
+ protected List Implements { get; } = [];
+
+ public abstract void Build(CodeWriter writer);
+
+ protected void SetName(string name)
+ {
+ Name = name;
+ }
+
+ public void AddProperty(PropertyBuilder property)
+ {
+ if (property is null)
+ {
+ throw new ArgumentNullException(nameof(property));
+ }
+
+ Properties.Add(property);
+ }
+
+ public void AddImplements(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException(nameof(value));
+ }
+
+ Implements.Add(value);
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ArrayBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ArrayBuilder.cs
new file mode 100644
index 0000000..9612bfe
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ArrayBuilder.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class ArrayBuilder : ICode
+{
+ private string? _prefix;
+ private string? _type;
+ private bool _determineStatement = true;
+ private bool _setReturn;
+ private readonly List _assignment = [];
+
+ private ArrayBuilder()
+ {
+ }
+
+ public ArrayBuilder SetType(string type)
+ {
+ _type = type;
+ return this;
+ }
+
+ public ArrayBuilder AddAssignment(ICode code)
+ {
+ _assignment.Add(code);
+ return this;
+ }
+
+ public ArrayBuilder SetDetermineStatement(bool value)
+ {
+ _determineStatement = value;
+ return this;
+ }
+
+ public ArrayBuilder SetPrefix(string prefix)
+ {
+ _prefix = prefix;
+ return this;
+ }
+
+ public ArrayBuilder SetReturn(bool value = true)
+ {
+ _setReturn = value;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (_type is null)
+ {
+ throw new ArgumentNullException(nameof(_type));
+ }
+
+ if (_determineStatement)
+ {
+ writer.WriteIndent();
+ }
+
+ if (_setReturn)
+ {
+ writer.Write("return ");
+ }
+
+ writer.Write(_prefix);
+
+ writer.Write("new ");
+ writer.Write(_type);
+ writer.Write("[] {");
+ writer.WriteLine();
+
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _assignment.Count; i++)
+ {
+ writer.WriteIndent();
+ _assignment[i].Build(writer);
+ if (i != _assignment.Count - 1)
+ {
+ writer.Write(",");
+ writer.WriteLine();
+ }
+ }
+ }
+
+ writer.WriteLine();
+ writer.WriteIndent();
+ writer.Write("}");
+
+ if (_determineStatement)
+ {
+ writer.Write(";");
+ writer.WriteLine();
+ }
+ }
+
+ public static ArrayBuilder New() => new ArrayBuilder();
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/AssignmentBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/AssignmentBuilder.cs
new file mode 100644
index 0000000..dbff7cc
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/AssignmentBuilder.cs
@@ -0,0 +1,122 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class AssignmentBuilder : ICode
+{
+ private ICode? _leftHandSide;
+ private ICode? _rightHandSide;
+ private bool _assertNonNull;
+ private string? _nonNullAssertTypeNameOverride;
+ private string _operator = "=";
+ private ICode? _assertException;
+
+ public static AssignmentBuilder New() => new();
+
+ public AssignmentBuilder SetLeftHandSide(ICode value)
+ {
+ _leftHandSide = value;
+ return this;
+ }
+
+ public AssignmentBuilder SetLeftHandSide(string value)
+ {
+ _leftHandSide = new CodeInlineBuilder().SetText(value);
+ return this;
+ }
+
+ public AssignmentBuilder SetOperator(string value)
+ {
+ _operator = value;
+ return this;
+ }
+
+ public AssignmentBuilder SetRightHandSide(ICode value)
+ {
+ _rightHandSide = value;
+ return this;
+ }
+
+ public AssignmentBuilder SetRightHandSide(string value)
+ {
+ _rightHandSide = new CodeInlineBuilder().SetText(value);
+ return this;
+ }
+
+ public AssignmentBuilder AssertNonNull(string? nonNullAssertTypeNameOverride = null)
+ {
+ _nonNullAssertTypeNameOverride = nonNullAssertTypeNameOverride;
+ return SetAssertNonNull(true);
+ }
+
+ public AssignmentBuilder SetAssertNonNull(bool value = true)
+ {
+ _assertNonNull = value;
+ return this;
+ }
+
+ public AssignmentBuilder SetAssertException(string code)
+ {
+ _assertException = CodeInlineBuilder.From($"throw new {code}");
+ return this;
+ }
+
+ public AssignmentBuilder SetAssertException(ExceptionBuilder code)
+ {
+ _assertException = code;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_leftHandSide is null || _rightHandSide is null)
+ {
+ throw new CodeGeneratorException("Assignment statement is not complete.");
+ }
+
+ writer.WriteIndent();
+ _leftHandSide.Build(writer);
+
+ writer.Write(" ");
+ writer.Write(_operator);
+ writer.Write(" ");
+
+ _rightHandSide.Build(writer);
+ if (_assertNonNull)
+ {
+ writer.WriteLine();
+ using (writer.IncreaseIndent())
+ {
+ writer.WriteIndent();
+ writer.Write(" ?? ");
+
+ if (_assertException is null)
+ {
+ writer.Write($"throw new {TypeNames.ArgumentNullException}(nameof(");
+ if (_nonNullAssertTypeNameOverride is not null)
+ {
+ writer.Write(_nonNullAssertTypeNameOverride);
+ }
+ else
+ {
+ _rightHandSide.Build(writer);
+ }
+
+ writer.Write("))");
+ }
+ else
+ {
+ _assertException.Build(writer);
+ }
+ }
+ }
+
+ writer.Write(";");
+ writer.WriteLine();
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CatchBlockBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CatchBlockBuilder.cs
new file mode 100644
index 0000000..ba081f2
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CatchBlockBuilder.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class CatchBlockBuilder : ICode
+{
+ private string? _exception;
+ private string? _exceptionVariable;
+ private readonly List _code = [];
+
+ public static CatchBlockBuilder New() => new();
+
+ public CatchBlockBuilder AddCode(ICode code)
+ {
+ _code.Add(code);
+ return this;
+ }
+
+ public CatchBlockBuilder SetExceptionVariable(string name)
+ {
+ if (_exception is null)
+ {
+ _exception = TypeNames.Exception;
+ }
+
+ _exceptionVariable = name;
+ return this;
+ }
+
+ public CatchBlockBuilder SetExceptionType(string typeName)
+ {
+ _exception = typeName;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ writer.WriteIndent();
+ writer.Write($"catch");
+ if (_exception is not null)
+ {
+ writer.Write("(");
+ writer.Write(_exception);
+
+ if (_exceptionVariable is not null)
+ {
+ writer.WriteSpace();
+ writer.Write(_exceptionVariable);
+ }
+
+ writer.Write(")");
+ }
+ writer.WriteLine();
+
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ foreach (var code in _code)
+ {
+ code.Build(writer);
+ }
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ClassBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ClassBuilder.cs
new file mode 100644
index 0000000..38ca386
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ClassBuilder.cs
@@ -0,0 +1,272 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class ClassBuilder : AbstractTypeBuilder
+{
+ private AccessModifier _accessModifier;
+ private readonly bool _isPartial = true;
+ private bool _isStatic;
+ private bool _isSealed;
+ private bool _isAbstract;
+ private string? _name;
+ private XmlCommentBuilder? _xmlComment;
+ private readonly List _fields = [];
+ private readonly List _constructors = [];
+ private readonly List _methods = [];
+ private readonly List _classes = [];
+
+ public static ClassBuilder New() => new();
+
+ public static ClassBuilder New(string className) => new ClassBuilder().SetName(className);
+
+ public ClassBuilder SetAccessModifier(AccessModifier value)
+ {
+ _accessModifier = value;
+ return this;
+ }
+
+ public new ClassBuilder SetName(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException(nameof(value));
+ }
+
+ _name = value;
+ return this;
+ }
+
+ public new ClassBuilder AddImplements(string value)
+ {
+ base.AddImplements(value);
+ return this;
+ }
+
+ public ClassBuilder SetComment(string? xmlComment)
+ {
+ if (xmlComment is not null)
+ {
+ _xmlComment = XmlCommentBuilder.New().SetSummary(xmlComment);
+ }
+
+ return this;
+ }
+
+ public ClassBuilder SetComment(XmlCommentBuilder? xmlComment)
+ {
+ if (xmlComment is not null)
+ {
+ _xmlComment = xmlComment;
+ }
+
+ return this;
+ }
+
+ public ClassBuilder AddConstructor(ConstructorBuilder constructor)
+ {
+ if (constructor is null)
+ {
+ throw new ArgumentNullException(nameof(constructor));
+ }
+
+ _constructors.Add(constructor);
+ return this;
+ }
+
+ public ClassBuilder AddField(FieldBuilder field)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ _fields.Add(field);
+ return this;
+ }
+
+ public new ClassBuilder AddProperty(PropertyBuilder property)
+ {
+ base.AddProperty(property);
+ return this;
+ }
+
+ public ClassBuilder AddMethod(MethodBuilder method)
+ {
+ if (method is null)
+ {
+ throw new ArgumentNullException(nameof(method));
+ }
+
+ _methods.Add(method);
+ return this;
+ }
+
+ public ClassBuilder AddClass(ClassBuilder classBuilder)
+ {
+ if (classBuilder is null)
+ {
+ throw new ArgumentNullException(nameof(classBuilder));
+ }
+
+ _classes.Add(classBuilder);
+ return this;
+ }
+
+ public ClassBuilder AddClass(string @class)
+ {
+ if (@class is null)
+ {
+ throw new ArgumentNullException(nameof(@class));
+ }
+
+ _classes.Add(CodeInlineBuilder.From(@class));
+ return this;
+ }
+
+ public ClassBuilder SetStatic()
+ {
+ _isStatic = true;
+ _isSealed = false;
+ _isAbstract = false;
+ return this;
+ }
+
+ public ClassBuilder SetSealed()
+ {
+ _isStatic = false;
+ _isSealed = true;
+ _isAbstract = false;
+ return this;
+ }
+
+ public ClassBuilder SetAbstract()
+ {
+ _isStatic = false;
+ _isSealed = false;
+ _isAbstract = true;
+ return this;
+ }
+
+ public override void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ _xmlComment?.Build(writer);
+
+ writer.WriteGeneratedAttribute();
+
+ var modifier = _accessModifier.ToString().ToLowerInvariant();
+
+ writer.WriteIndent();
+
+ writer.Write($"{modifier} ");
+
+ if (_isStatic)
+ {
+ writer.Write("static ");
+ }
+ else if (_isSealed)
+ {
+ writer.Write("sealed ");
+ }
+ else if (_isAbstract)
+ {
+ writer.Write("abstract ");
+ }
+
+ if (_isPartial)
+ {
+ writer.Write("partial ");
+ }
+
+ writer.Write("class ");
+ writer.WriteLine(_name);
+
+ if (!_isStatic && Implements.Count > 0)
+ {
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < Implements.Count; i++)
+ {
+ writer.WriteIndentedLine(i == 0
+ ? $": {Implements[i]}"
+ : $", {Implements[i]}");
+ }
+ }
+ }
+
+ writer.WriteIndentedLine("{");
+
+ var writeLine = false;
+
+ using (writer.IncreaseIndent())
+ {
+ if (_fields.Count > 0)
+ {
+ foreach (var builder in _fields)
+ {
+ builder.Build(writer);
+ }
+
+ writeLine = true;
+ }
+
+ if (_constructors.Count > 0)
+ {
+ for (var i = 0; i < _constructors.Count; i++)
+ {
+ if (writeLine || i > 0)
+ {
+ writer.WriteLine();
+ }
+
+ _constructors[i]
+ .SetTypeName(_name!)
+ .Build(writer);
+ }
+
+ writeLine = true;
+ }
+
+ if (Properties.Count > 0)
+ {
+ for (var i = 0; i < Properties.Count; i++)
+ {
+ if (writeLine || i > 0)
+ {
+ writer.WriteLine();
+ }
+
+ Properties[i].Build(writer);
+ }
+
+ writeLine = true;
+ }
+
+ if (_methods.Count > 0)
+ {
+ for (var i = 0; i < _methods.Count; i++)
+ {
+ if (writeLine || i > 0)
+ {
+ writer.WriteLine();
+ }
+
+ _methods[i].Build(writer);
+ }
+ }
+ }
+
+ foreach (var classBuilder in _classes)
+ {
+ classBuilder.Build(writer);
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeBlockBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeBlockBuilder.cs
new file mode 100644
index 0000000..c703eda
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeBlockBuilder.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class CodeBlockBuilder : ICode
+{
+ private readonly List _blockParts = [];
+
+ public static CodeBlockBuilder New() => new CodeBlockBuilder();
+
+ public static CodeBlockBuilder From(StringBuilder sourceText)
+ {
+ var builder = New();
+
+ using var stringReader = new StringReader(sourceText.ToString());
+
+ string? line = null;
+
+ do
+ {
+ line = stringReader.ReadLine();
+
+ if (line is not null)
+ {
+ builder.AddCode(CodeLineBuilder.From(line));
+ }
+ } while (line is not null);
+
+ return builder;
+ }
+
+ public CodeBlockBuilder AddCode(ICodeBuilder value)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _blockParts.Add(value);
+ return this;
+ }
+
+ public CodeBlockBuilder AddCode(string value)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _blockParts.Add(CodeInlineBuilder.New().SetText(value));
+ return this;
+ }
+
+ public CodeBlockBuilder AddEmptyLine()
+ {
+ _blockParts.Add(CodeLineBuilder.New());
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ foreach (var code in _blockParts)
+ {
+ code.Build(writer);
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeFileBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeFileBuilder.cs
new file mode 100644
index 0000000..6edff13
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeFileBuilder.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class CodeFileBuilder : ICodeBuilder
+{
+ private readonly List _usings = [];
+ private string? _namespace;
+ private readonly List _types = [];
+
+ public static CodeFileBuilder New() => new();
+
+ public CodeFileBuilder AddUsing(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException(nameof(value));
+ }
+
+ _usings.Add(value);
+ return this;
+ }
+
+ public CodeFileBuilder SetNamespace(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException(nameof(value));
+ }
+
+ _namespace = value;
+ return this;
+ }
+
+ public CodeFileBuilder AddType(ITypeBuilder value)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _types.Add(value);
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_types.Count == 0 && _usings.Count == 0)
+ {
+ return;
+ }
+
+ if (_namespace is null)
+ {
+ throw new CodeGeneratorException("Namespace cannot be null.");
+ }
+
+ BuildInternal(writer);
+ }
+
+ private void BuildInternal(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_types.Count == 0 && _usings.Count == 0)
+ {
+ return;
+ }
+
+ if (_namespace is null)
+ {
+ throw new CodeGeneratorException("Namespace cannot be null.");
+ }
+
+ if (_usings.Count > 0)
+ {
+ foreach (var u in _usings)
+ {
+ writer.WriteIndentedLine($"using {u};");
+ }
+ writer.WriteLine();
+ }
+
+ writer.WriteIndentedLine("#nullable enable");
+ writer.WriteLine();
+
+ writer.WriteIndentedLine($"namespace {_namespace}");
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ foreach (var type in _types)
+ {
+ type.Build(writer);
+ }
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeInlineBlockBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeInlineBlockBuilder.cs
new file mode 100644
index 0000000..7159b46
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeInlineBlockBuilder.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class CodeInlineBlockBuilder : ICode
+{
+ private readonly List _lineParts = [];
+
+ public static CodeInlineBlockBuilder New() => new();
+
+ public CodeInlineBlockBuilder AddCode(ICode value)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _lineParts.Add(value);
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ foreach (var code in _lineParts)
+ {
+ code.Build(writer);
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeInlineBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeInlineBuilder.cs
new file mode 100644
index 0000000..53dc833
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeInlineBuilder.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class CodeInlineBuilder : ICode
+{
+ private string? _value;
+
+ public static CodeInlineBuilder New() => new();
+
+ public static CodeInlineBuilder From(string sourceText) =>
+ New().SetText(sourceText);
+
+ public CodeInlineBuilder SetText(string value)
+ {
+ _value = value;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_value is null)
+ {
+ return;
+ }
+
+ writer.Write(_value);
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeLineBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeLineBuilder.cs
new file mode 100644
index 0000000..559a3a6
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/CodeLineBuilder.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class CodeLineBuilder : ICode
+{
+ private bool _writeLine = true;
+ private ICode? _value;
+ private string? _sourceText;
+
+ public static CodeLineBuilder New() => new CodeLineBuilder();
+
+ public static CodeLineBuilder From(string line) => New().SetLine(line);
+
+ public CodeLineBuilder SetLine(string value)
+ {
+ _sourceText = value;
+ _value = null;
+ return this;
+ }
+
+ public CodeLineBuilder SetLine(ICode value)
+ {
+ _value = value;
+ _sourceText = null;
+ return this;
+ }
+
+ public CodeLineBuilder SetWriteLine(bool writeLine)
+ {
+ _writeLine = writeLine;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_value is not null)
+ {
+ writer.WriteIndent();
+ _value.Build(writer);
+ }
+ else if (_sourceText is not null)
+ {
+ writer.WriteIndent();
+ writer.Write(_sourceText);
+ }
+
+ if (_writeLine)
+ {
+ writer.WriteLine();
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ConditionBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ConditionBuilder.cs
new file mode 100644
index 0000000..c4c920f
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ConditionBuilder.cs
@@ -0,0 +1,115 @@
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class ConditionBuilder : ICode
+{
+ private readonly List _conditions = [];
+ private bool _setReturn;
+ private bool _determineStatement;
+ public static ConditionBuilder New() => new();
+
+ public ConditionBuilder Set(string condition)
+ {
+ _conditions.Add(CodeInlineBuilder.New().SetText(condition));
+ return this;
+ }
+
+ public ConditionBuilder SetReturn(bool value = true)
+ {
+ _setReturn = value;
+ return this;
+ }
+
+ public ConditionBuilder Set(ICode condition)
+ {
+ _conditions.Add(condition);
+ return this;
+ }
+
+ public ConditionBuilder SetDetermineStatement(bool value = true)
+ {
+ _determineStatement = value;
+ return this;
+ }
+
+ public ConditionBuilder And(string condition, bool applyIf = true)
+ {
+ return applyIf ? And(CodeInlineBuilder.New().SetText(condition)) : this;
+ }
+
+ public ConditionBuilder And(ICode condition)
+ {
+ if (_conditions.Count == 0)
+ {
+ return Set(condition);
+ }
+
+ _conditions.Add(
+ CodeBlockBuilder.New()
+ .AddCode(CodeInlineBuilder.New().SetText("&& "))
+ .AddCode(condition));
+ return this;
+ }
+
+ public ConditionBuilder Or(ICode condition)
+ {
+ if (_conditions.Count == 0)
+ {
+ return Set(condition);
+ }
+
+ _conditions.Add(
+ CodeBlockBuilder.New()
+ .AddCode(CodeInlineBuilder.New().SetText("|| "))
+ .AddCode(condition));
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (_determineStatement)
+ {
+ writer.WriteIndent();
+ }
+
+ if (_setReturn)
+ {
+ writer.Write("return ");
+ }
+
+ if (_conditions.Count != 0)
+ {
+ using (writer.IncreaseIndent())
+ {
+ WriteCondition(writer, _conditions[0]);
+ for (var i = 1; i < _conditions.Count; i++)
+ {
+ CodeLineBuilder.New().Build(writer);
+ writer.WriteIndent();
+ WriteCondition(writer, _conditions[i]);
+ }
+ }
+ }
+
+ if (_determineStatement)
+ {
+ writer.Write(";");
+ writer.WriteLine();
+ }
+ }
+
+ private void WriteCondition(CodeWriter writer, ICode condition)
+ {
+ if (condition is ConditionBuilder)
+ {
+ writer.Write("(");
+ condition.Build(writer);
+ writer.Write(")");
+ }
+ else
+ {
+ condition.Build(writer);
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ConstructorBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ConstructorBuilder.cs
new file mode 100644
index 0000000..69db927
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ConstructorBuilder.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class ConstructorBuilder : ICodeBuilder
+{
+ private AccessModifier _accessModifier = AccessModifier.Public;
+ private string? _typeName;
+ private readonly List _parameters = [];
+ private readonly List _lines = [];
+ private readonly List _base = [];
+
+ public static ConstructorBuilder New() => new ConstructorBuilder();
+
+ public ConstructorBuilder SetAccessModifier(AccessModifier value)
+ {
+ _accessModifier = value;
+ return this;
+ }
+
+ public ConstructorBuilder SetTypeName(string value)
+ {
+ _typeName = value;
+ return this;
+ }
+
+ public ConstructorBuilder AddParameter(ParameterBuilder value)
+ {
+ _parameters.Add(value);
+ return this;
+ }
+
+ public bool HasParameters()
+ {
+ return _parameters.Any();
+ }
+
+ public ConstructorBuilder AddCode(ICode value)
+ {
+ _lines.Add(value);
+ return this;
+ }
+
+ public ConstructorBuilder AddCode(string value)
+ {
+ _lines.Add(CodeLineBuilder.New().SetLine(value));
+ return this;
+ }
+
+ public ConstructorBuilder AddBase(ICode value)
+ {
+ _base.Add(value);
+ return this;
+ }
+
+ public ConstructorBuilder AddBase(string value)
+ {
+ _base.Add(CodeInlineBuilder.New().SetText(value));
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ var modifier = _accessModifier.ToString().ToLowerInvariant();
+
+ writer.WriteIndent();
+
+ writer.Write($"{modifier} {_typeName}(");
+
+ if (_parameters.Count == 0)
+ {
+ writer.Write(")");
+ }
+ else if (_parameters.Count == 1)
+ {
+ _parameters[0].Build(writer);
+ writer.Write(")");
+ }
+ else
+ {
+ writer.WriteLine();
+
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _parameters.Count; i++)
+ {
+ writer.WriteIndent();
+ _parameters[i].Build(writer);
+ if (i == _parameters.Count - 1)
+ {
+ writer.Write(")");
+ }
+ else
+ {
+ writer.Write(",");
+ writer.WriteLine();
+ }
+ }
+ }
+ }
+
+ if (_base.Count > 0)
+ {
+ writer.WriteLine();
+
+ using (writer.IncreaseIndent())
+ {
+ writer.WriteIndent();
+ writer.Write(": base(");
+ if (_base.Count == 1)
+ {
+ _base[0].Build(writer);
+ writer.Write(")");
+ }
+ else
+ {
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _base.Count; i++)
+ {
+ writer.WriteLine();
+ writer.WriteIndent();
+ _base[i].Build(writer);
+ if (i == _base.Count - 1)
+ {
+ writer.Write(")");
+ }
+ else
+ {
+ writer.Write(",");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ writer.WriteLine();
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ foreach (var code in _lines)
+ {
+ code.Build(writer);
+ }
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/EnumBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/EnumBuilder.cs
new file mode 100644
index 0000000..bbebf5c
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/EnumBuilder.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class EnumBuilder : ITypeBuilder
+{
+ private AccessModifier _accessModifier;
+ private readonly List<(string, long?, XmlCommentBuilder)> _elements = [];
+ private string? _name;
+ private string? _underlyingType;
+ private XmlCommentBuilder? _xmlComment;
+
+ public static EnumBuilder New() => new();
+
+ public EnumBuilder SetAccessModifier(AccessModifier value)
+ {
+ _accessModifier = value;
+ return this;
+ }
+
+ public EnumBuilder SetName(string value)
+ {
+ _name = value;
+ return this;
+ }
+
+ public EnumBuilder SetUnderlyingType(RuntimeTypeInfo? value)
+ {
+ _underlyingType = value?.ToString();
+ return this;
+ }
+
+ public EnumBuilder SetUnderlyingType(string? value)
+ {
+ _underlyingType = value;
+ return this;
+ }
+
+ public EnumBuilder AddElement(string name, long? value = null, string? documentation = null)
+ {
+ _elements.Add((
+ name,
+ value,
+ documentation is null
+ ? null
+ : XmlCommentBuilder.New().SetSummary(documentation)));
+ return this;
+ }
+
+ public EnumBuilder SetComment(string? xmlComment)
+ {
+ if (xmlComment is not null)
+ {
+ _xmlComment = XmlCommentBuilder.New().SetSummary(xmlComment);
+ }
+
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ _xmlComment?.Build(writer);
+
+ writer.WriteGeneratedAttribute();
+
+ var modifier = _accessModifier.ToString().ToLowerInvariant();
+
+ if (_underlyingType is null)
+ {
+ writer.WriteIndentedLine($"{modifier} enum {_name}");
+ }
+ else
+ {
+ writer.WriteIndentedLine($"{modifier} enum {_name} : {_underlyingType}");
+ }
+
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _elements.Count; i++)
+ {
+ _elements[i].Item3?.Build(writer);
+
+ writer.WriteIndent();
+ writer.Write(_elements[i].Item1);
+
+ if (_elements[i].Item2.HasValue)
+ {
+ writer.Write($" = {_elements[i].Item2}");
+ }
+
+ if (i + 1 < _elements.Count)
+ {
+ writer.Write($",");
+ }
+
+ writer.WriteLine();
+ }
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ExceptionBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ExceptionBuilder.cs
new file mode 100644
index 0000000..bd4c1b1
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ExceptionBuilder.cs
@@ -0,0 +1,52 @@
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class ExceptionBuilder : ICode
+{
+ private readonly MethodCallBuilder _method =
+ MethodCallBuilder
+ .New()
+ .SetPrefix("throw new ");
+
+ public ExceptionBuilder SetException(string exception)
+ {
+ _method.SetMethodName(exception);
+ return this;
+ }
+
+ public ExceptionBuilder AddArgument(string value)
+ {
+ _method.AddArgument(value);
+ return this;
+ }
+
+ public ExceptionBuilder AddArgument(ICode value)
+ {
+ _method.AddArgument(value);
+ return this;
+ }
+
+ public ExceptionBuilder SetDetermineStatement(bool value)
+ {
+ _method.SetDetermineStatement(value);
+ return this;
+ }
+
+ public ExceptionBuilder SetWrapArguments(bool value = true)
+ {
+ _method.SetWrapArguments(value);
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ _method.Build(writer);
+ }
+
+ public static ExceptionBuilder New() => new();
+
+ public static ExceptionBuilder New(string types) =>
+ new ExceptionBuilder().SetException(types);
+
+ public static ExceptionBuilder Inline(string types) =>
+ new ExceptionBuilder().SetException(types).SetDetermineStatement(false);
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/FieldBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/FieldBuilder.cs
new file mode 100644
index 0000000..36bade5
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/FieldBuilder.cs
@@ -0,0 +1,147 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class FieldBuilder : ICodeBuilder
+{
+ private AccessModifier _accessModifier = AccessModifier.Private;
+ private bool _isConst;
+ private bool _isStatic;
+ private bool _isReadOnly;
+ private TypeReferenceBuilder? _type;
+ private string? _name;
+ private ICode? _value;
+ private bool _useDefaultInitializer;
+ private bool _beginValueWithNewline;
+
+ public static FieldBuilder New() => new FieldBuilder();
+
+ public FieldBuilder SetAccessModifier(AccessModifier value)
+ {
+ _accessModifier = value;
+ return this;
+ }
+
+ public FieldBuilder SetType(string value, bool condition = true)
+ {
+ if (condition)
+ {
+ _type = TypeReferenceBuilder.New().SetName(value);
+ }
+
+ return this;
+ }
+
+ public FieldBuilder SetType(TypeReferenceBuilder typeReference)
+ {
+ _type = typeReference;
+ return this;
+ }
+
+ public FieldBuilder SetName(string value)
+ {
+ _name = value;
+ return this;
+ }
+
+ public FieldBuilder SetConst()
+ {
+ _isConst = true;
+ _isStatic = false;
+ _isReadOnly = false;
+ return this;
+ }
+
+ public FieldBuilder SetStatic()
+ {
+ _isStatic = true;
+ _isConst = false;
+ return this;
+ }
+
+ public FieldBuilder SetReadOnly()
+ {
+ _isReadOnly = true;
+ _isConst = false;
+ return this;
+ }
+
+ public FieldBuilder SetValue(string? value, bool beginValueWithNewline = false)
+ {
+ return SetValue(
+ value is not null ? CodeInlineBuilder.From(value) : null,
+ beginValueWithNewline);
+ }
+
+ public FieldBuilder SetValue(ICode? value, bool beginValueWithNewline = false)
+ {
+ _value = value;
+ _beginValueWithNewline = beginValueWithNewline;
+ _useDefaultInitializer = false;
+ return this;
+ }
+
+ public FieldBuilder UseDefaultInitializer()
+ {
+ _value = null;
+ _useDefaultInitializer = true;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_type is null)
+ {
+ throw new ArgumentNullException(nameof(_type));
+ }
+
+ var modifier = _accessModifier.ToString().ToLowerInvariant();
+
+ writer.WriteIndent();
+ writer.Write($"{modifier} ");
+
+ if (_isConst)
+ {
+ writer.Write("const ");
+ }
+
+ if (_isStatic)
+ {
+ writer.Write("static ");
+ }
+
+ if (_isReadOnly)
+ {
+ writer.Write("readonly ");
+ }
+
+ _type.Build(writer);
+ writer.Write(_name);
+
+ if (_value is { })
+ {
+ writer.Write(" = ");
+ if (_beginValueWithNewline)
+ {
+ writer.WriteLine();
+ using (writer.IncreaseIndent())
+ {
+ writer.WriteIndent();
+ }
+ }
+
+ _value.Build(writer);
+ }
+ else if (_useDefaultInitializer)
+ {
+ writer.Write($" = new {_type}()");
+ }
+
+ writer.WriteLine(";");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ForEachBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ForEachBuilder.cs
new file mode 100644
index 0000000..c52f421
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ForEachBuilder.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class ForEachBuilder : ICodeContainer
+{
+ private ICode? _loopHeader;
+ private readonly List _lines = [];
+
+ public static ForEachBuilder New() => new();
+
+ public ForEachBuilder AddCode(string code, bool addIf = true)
+ {
+ AddCode(
+ CodeLineBuilder.New().SetLine(code),
+ addIf);
+ return this;
+ }
+
+ public ForEachBuilder AddCode(ICode code, bool addIf = true)
+ {
+ if (addIf)
+ {
+ _lines.Add(code);
+ }
+
+ return this;
+ }
+
+ public ForEachBuilder AddEmptyLine()
+ {
+ _lines.Add(CodeLineBuilder.New());
+ return this;
+ }
+
+ public ForEachBuilder SetLoopHeader(string elementCode)
+ {
+ _loopHeader = CodeInlineBuilder.New().SetText(elementCode);
+ return this;
+ }
+
+ public ForEachBuilder SetLoopHeader(ICode elementCode)
+ {
+ _loopHeader = elementCode;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ writer.WriteIndent();
+ writer.Write("foreach (");
+ _loopHeader?.Build(writer);
+ writer.Write(")");
+ writer.WriteLine();
+ writer.WriteIndent();
+ writer.WriteLine("{");
+ using (writer.IncreaseIndent())
+ {
+ foreach (var line in _lines)
+ {
+ line.Build(writer);
+ }
+ }
+
+ writer.WriteIndent();
+ writer.WriteLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/HashCodeBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/HashCodeBuilder.cs
new file mode 100644
index 0000000..d471243
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/HashCodeBuilder.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using StrawberryShake.CodeGeneration.CSharp.Builders;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Generators;
+
+internal class HashCodeBuilder : ICode
+{
+ public const string VariableName = "hash";
+ public const int Prime = 397;
+
+ private readonly List _code = [];
+
+ public static HashCodeBuilder New() => new();
+
+ public HashCodeBuilder AddCode(ICode code)
+ {
+ _code.Add(code);
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ writer.WriteIndentedLine("unchecked");
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ writer.WriteIndentedLine($"int {VariableName} = 5;");
+ writer.WriteLine();
+ foreach (var check in _code)
+ {
+ check.Build(writer);
+ writer.WriteLine();
+ }
+
+ writer.WriteIndentedLine($"return {VariableName};");
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICode.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICode.cs
new file mode 100644
index 0000000..4348e5f
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICode.cs
@@ -0,0 +1,3 @@
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public interface ICode : ICodeBuilder;
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICodeBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICodeBuilder.cs
new file mode 100644
index 0000000..397bbc8
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICodeBuilder.cs
@@ -0,0 +1,6 @@
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public interface ICodeBuilder
+{
+ void Build(CodeWriter writer);
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICodeContainer.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICodeContainer.cs
new file mode 100644
index 0000000..aa37501
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ICodeContainer.cs
@@ -0,0 +1,10 @@
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public interface ICodeContainer: ICode
+{
+ public T AddCode(string code, bool addIf = true);
+
+ public T AddCode(ICode code, bool addIf = true);
+
+ public T AddEmptyLine();
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ITypeBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ITypeBuilder.cs
new file mode 100644
index 0000000..8063a79
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ITypeBuilder.cs
@@ -0,0 +1,3 @@
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public interface ITypeBuilder : ICodeBuilder;
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/IfBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/IfBuilder.cs
new file mode 100644
index 0000000..04c9d64
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/IfBuilder.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class IfBuilder : ICodeContainer
+{
+ private readonly List _lines = [];
+ private ConditionBuilder? _condition;
+
+ private readonly List _ifElses = [];
+ private ICode? _elseCode;
+ private bool _writeIndents = true;
+
+ public static IfBuilder New() => new();
+
+ public IfBuilder SetCondition(ConditionBuilder condition)
+ {
+ _condition = condition;
+ return this;
+ }
+
+ public IfBuilder SkipIndents()
+ {
+ _writeIndents = false;
+ return this;
+ }
+
+ public IfBuilder SetCondition(string condition)
+ {
+ _condition = ConditionBuilder.New().Set(condition);
+ return this;
+ }
+
+ public IfBuilder SetCondition(ICode condition)
+ {
+ _condition = ConditionBuilder.New().Set(condition);
+ return this;
+ }
+
+ public IfBuilder AddCode(string code, bool addIf = true)
+ {
+ if (addIf)
+ {
+ _lines.Add(CodeLineBuilder.New().SetLine(code));
+ }
+
+ return this;
+ }
+
+ public IfBuilder AddCode(ICode code, bool addIf = true)
+ {
+ if (addIf)
+ {
+ _lines.Add(code);
+ }
+
+ return this;
+ }
+
+ public IfBuilder AddEmptyLine()
+ {
+ _lines.Add(CodeLineBuilder.New());
+ return this;
+ }
+
+ public IfBuilder AddIfElse(IfBuilder singleIf)
+ {
+ _ifElses.Add(singleIf);
+ return this;
+ }
+
+ public IfBuilder AddElse(ICode code)
+ {
+ _elseCode = code;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (_condition is null)
+ {
+ throw new ArgumentNullException(nameof(_condition));
+ }
+
+ if (_writeIndents)
+ {
+ writer.WriteIndent();
+ }
+
+ writer.Write("if (");
+ _condition.Build(writer);
+ writer.Write(")");
+ writer.WriteLine();
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ foreach (var code in _lines)
+ {
+ code.Build(writer);
+ }
+ }
+
+ writer.WriteIndent();
+ writer.Write("}");
+ writer.WriteLine();
+
+ foreach (var ifBuilder in _ifElses)
+ {
+ writer.WriteIndent();
+ writer.Write("else ");
+ ifBuilder.Build(writer);
+ }
+
+ if (_elseCode is not null)
+ {
+ writer.WriteIndentedLine("else");
+ writer.WriteIndentedLine("{");
+ using (writer.IncreaseIndent())
+ {
+ _elseCode.Build(writer);
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/Inheritance.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/Inheritance.cs
new file mode 100644
index 0000000..7501b20
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/Inheritance.cs
@@ -0,0 +1,9 @@
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public enum Inheritance
+{
+ None,
+ Sealed,
+ Override,
+ Virtual,
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/InterfaceBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/InterfaceBuilder.cs
new file mode 100644
index 0000000..3bcc90b
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/InterfaceBuilder.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class InterfaceBuilder : AbstractTypeBuilder
+{
+ private AccessModifier _accessModifier;
+ private readonly List _methods = [];
+
+ private XmlCommentBuilder? _xmlComment;
+
+ public static InterfaceBuilder New() => new();
+
+ public InterfaceBuilder SetAccessModifier(AccessModifier value)
+ {
+ _accessModifier = value;
+ return this;
+ }
+
+ public new InterfaceBuilder SetName(string name)
+ {
+ base.SetName(name);
+ return this;
+ }
+
+ public new InterfaceBuilder AddImplements(string value)
+ {
+ base.AddImplements(value);
+ return this;
+ }
+
+ public new InterfaceBuilder AddProperty(PropertyBuilder property)
+ {
+ base.AddProperty(property);
+ return this;
+ }
+
+ public InterfaceBuilder SetComment(string? xmlComment)
+ {
+ if (xmlComment is not null)
+ {
+ _xmlComment = XmlCommentBuilder.New().SetSummary(xmlComment);
+ }
+
+ return this;
+ }
+
+ public InterfaceBuilder SetComment(XmlCommentBuilder? xmlComment)
+ {
+ if (xmlComment is not null)
+ {
+ _xmlComment = xmlComment;
+ }
+
+ return this;
+ }
+
+ public InterfaceBuilder AddMethod(MethodBuilder method)
+ {
+ if (method is null)
+ {
+ throw new ArgumentNullException(nameof(method));
+ }
+
+ _methods.Add(method);
+ return this;
+ }
+
+ public override void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ _xmlComment?.Build(writer);
+
+ writer.WriteGeneratedAttribute();
+
+ writer.WriteIndent();
+
+ var modifier = _accessModifier.ToString().ToLowerInvariant();
+
+ writer.Write($"{modifier} partial interface ");
+ writer.WriteLine(Name);
+
+ if (Implements.Count > 0)
+ {
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < Implements.Count; i++)
+ {
+ writer.WriteIndentedLine(
+ i == 0
+ ? $": {Implements[i]}"
+ : $", {Implements[i]}");
+ }
+ }
+ }
+
+ writer.WriteIndentedLine("{");
+
+ var writeLine = false;
+
+ using (writer.IncreaseIndent())
+ {
+ if (Properties.Count > 0)
+ {
+ for (var i = 0; i < Properties.Count; i++)
+ {
+ if (writeLine || i > 0)
+ {
+ writer.WriteLine();
+ }
+
+ Properties[i].Build(writer);
+ }
+
+ writeLine = true;
+ }
+
+ if (_methods.Count > 0)
+ {
+ for (var i = 0; i < _methods.Count; i++)
+ {
+ if (writeLine || i > 0)
+ {
+ writer.WriteLine();
+ }
+
+ _methods[i].Build(writer);
+ }
+ }
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/LambdaBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/LambdaBuilder.cs
new file mode 100644
index 0000000..d2315ac
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/LambdaBuilder.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class LambdaBuilder : ICode
+{
+ private bool _block;
+ private bool _isAsync;
+ private readonly List _arguments = [];
+ private ICode? _code;
+
+ public LambdaBuilder AddArgument(string value)
+ {
+ _arguments.Add(value);
+ return this;
+ }
+
+ public LambdaBuilder SetCode(ICode code)
+ {
+ _code = code;
+ return this;
+ }
+
+ public LambdaBuilder SetBlock(bool block)
+ {
+ _block = block;
+ return this;
+ }
+
+ public LambdaBuilder SetAsync(bool value = true)
+ {
+ _isAsync = value;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (_code is null)
+ {
+ throw new ArgumentNullException(nameof(_code));
+ }
+
+ if (_isAsync)
+ {
+ writer.Write("async ");
+ }
+
+ if (_arguments.Count > 1)
+ {
+ writer.Write('(');
+ }
+
+ for (var i = 0; i < _arguments.Count; i++)
+ {
+ if (i > 0)
+ {
+ writer.Write(',');
+ }
+
+ writer.Write(_arguments[i]);
+ }
+
+ if (_arguments.Count > 1)
+ {
+ writer.Write(')');
+ }
+
+ if (_arguments.Count == 0)
+ {
+ writer.Write("()");
+ }
+
+ writer.Write(" => ");
+
+ if (_block)
+ {
+ writer.WriteLine();
+ writer.WriteIndent();
+ writer.WriteLeftBrace();
+ writer.WriteLine();
+ writer.IncreaseIndent();
+ }
+
+ _code.Build(writer);
+
+ if (_block)
+ {
+ writer.DecreaseIndent();
+ writer.WriteIndent();
+ writer.WriteRightBrace();
+ }
+ }
+
+ public static LambdaBuilder New() => new();
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/MethodBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/MethodBuilder.cs
new file mode 100644
index 0000000..245c55d
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/MethodBuilder.cs
@@ -0,0 +1,245 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class MethodBuilder : ICodeContainer
+{
+ private AccessModifier _accessModifier = AccessModifier.Private;
+ private Inheritance _inheritance = Inheritance.None;
+ private bool _isStatic;
+ private bool _isOnlyDeclaration;
+ private bool _isOverride;
+ private bool _is;
+ private TypeReferenceBuilder _returnType = TypeReferenceBuilder.New().SetName("void");
+ private string? _name;
+ private readonly List _parameters = [];
+ private readonly List _lines = [];
+ private bool _isAsync;
+ private string? _interface;
+
+ public static MethodBuilder New() => new();
+
+ public MethodBuilder SetAccessModifier(AccessModifier value)
+ {
+ _accessModifier = value;
+ return this;
+ }
+
+ public MethodBuilder SetStatic()
+ {
+ _isStatic = true;
+ return this;
+ }
+
+ public MethodBuilder SetAsync()
+ {
+ _isAsync = true;
+ return this;
+ }
+
+ public MethodBuilder SetInterface(string value)
+ {
+ _interface = value;
+ return this;
+ }
+
+ public MethodBuilder SetOverride()
+ {
+ _isOverride = true;
+ return this;
+ }
+
+ public MethodBuilder Set()
+ {
+ _is = true;
+ return this;
+ }
+
+ public MethodBuilder SetInheritance(Inheritance value)
+ {
+ _inheritance = value;
+ return this;
+ }
+
+ public MethodBuilder SetOnlyDeclaration(bool value = true)
+ {
+ _isOnlyDeclaration = value;
+ return this;
+ }
+
+ public MethodBuilder SetReturnType(string value, bool condition = true)
+ {
+ if (condition)
+ {
+ _returnType = TypeReferenceBuilder.New().SetName(value);
+ }
+
+ return this;
+ }
+
+ public MethodBuilder SetReturnType(TypeReferenceBuilder value, bool condition = true)
+ {
+ if (condition)
+ {
+ _returnType = value;
+ }
+
+ return this;
+ }
+
+ public MethodBuilder SetName(string value)
+ {
+ _name = value;
+ return this;
+ }
+
+ public MethodBuilder AddParameter(ParameterBuilder value)
+ {
+ _parameters.Add(value);
+ return this;
+ }
+
+ public MethodBuilder AddCode(string code, bool addIf = true)
+ {
+ if (addIf)
+ {
+ _lines.Add(CodeLineBuilder.New().SetLine(code));
+ }
+
+ return this;
+ }
+
+ public MethodBuilder AddCode(ICode code, bool addIf = true)
+ {
+ if (addIf)
+ {
+ _lines.Add(code);
+ }
+
+ return this;
+ }
+
+ public MethodBuilder AddEmptyLine()
+ {
+ _lines.Add(CodeLineBuilder.New());
+ return this;
+ }
+
+ public MethodBuilder AddInlineCode(string code)
+ {
+ _lines.Add(CodeInlineBuilder.New().SetText(code));
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ var modifier = _accessModifier.ToString().ToLowerInvariant();
+
+ writer.WriteIndent();
+
+ if (_interface is null && !_isOnlyDeclaration)
+ {
+ writer.Write($"{modifier} ");
+
+ if (_isStatic)
+ {
+ writer.Write("static ");
+ }
+
+ if (_isOverride)
+ {
+ writer.Write("override ");
+ }
+
+ if (_isAsync)
+ {
+ writer.Write("async ");
+ }
+
+ if (_is)
+ {
+ writer.Write(" ");
+ }
+
+ writer.Write($"{CreateInheritance()}");
+ }
+
+ _returnType.Build(writer);
+
+ if (_interface is not null)
+ {
+ writer.Write(_interface);
+ writer.Write(".");
+ }
+
+ writer.Write($"{_name}(");
+
+ if (_parameters.Count == 0)
+ {
+ writer.Write(")");
+ }
+ else if (_parameters.Count == 1)
+ {
+ _parameters[0].Build(writer);
+ writer.Write(")");
+ }
+ else
+ {
+ writer.WriteLine();
+
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _parameters.Count; i++)
+ {
+ writer.WriteIndent();
+ _parameters[i].Build(writer);
+ if (i == _parameters.Count - 1)
+ {
+ writer.Write(")");
+ }
+ else
+ {
+ writer.Write(",");
+ writer.WriteLine();
+ }
+ }
+ }
+ }
+
+ if (_isOnlyDeclaration)
+ {
+ writer.Write(";");
+ writer.WriteLine();
+ }
+ else
+ {
+ writer.WriteLine();
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ foreach (var code in _lines)
+ {
+ code.Build(writer);
+ }
+ }
+
+ writer.WriteIndentedLine("}");
+ }
+ }
+
+ private string CreateInheritance()
+ => _inheritance switch
+ {
+ Inheritance.Override => "override ",
+ Inheritance.Sealed => "sealed override ",
+ Inheritance.Virtual => "virtual ",
+ _ => string.Empty,
+ };
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/MethodCallBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/MethodCallBuilder.cs
new file mode 100644
index 0000000..f0d7228
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/MethodCallBuilder.cs
@@ -0,0 +1,275 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class MethodCallBuilder : ICode
+{
+ private string[] _methodName = [];
+ private bool _determineStatement = true;
+ private bool _setNullForgiving;
+ private bool _wrapArguments;
+ private bool _setReturn;
+ private bool _setNew;
+ private bool _setAwait;
+ private string? _prefix;
+ private readonly List _arguments = [];
+ private readonly List _generics = [];
+ private readonly List _chainedCode = [];
+
+ public static MethodCallBuilder New() => new();
+
+ public static MethodCallBuilder Inline() => New().SetDetermineStatement(false);
+
+ public MethodCallBuilder SetPrefix(string prefix)
+ {
+ _prefix = prefix;
+ return this;
+ }
+
+ public MethodCallBuilder SetMethodName(string methodName)
+ {
+ _methodName =
+ [
+ methodName,
+ ];
+ return this;
+ }
+
+ public MethodCallBuilder SetMethodName(params string[] methodName)
+ {
+ _methodName = methodName;
+ return this;
+ }
+
+ public MethodCallBuilder AddChainedCode(ICode value)
+ {
+ _chainedCode.Add(value);
+ return this;
+ }
+
+ public MethodCallBuilder AddArgument(ICode value)
+ {
+ _arguments.Add(value);
+ return this;
+ }
+
+ public MethodCallBuilder AddGeneric(ICode value)
+ {
+ _generics.Add(value);
+ return this;
+ }
+
+ public MethodCallBuilder AddGeneric(string value)
+ {
+ _generics.Add(CodeInlineBuilder.New().SetText(value));
+ return this;
+ }
+
+ public MethodCallBuilder AddArgument(string value)
+ {
+ _arguments.Add(CodeInlineBuilder.New().SetText(value));
+ return this;
+ }
+
+ public MethodCallBuilder AddOutArgument(
+ string value,
+ string typeReference)
+ {
+ _arguments.Add(CodeInlineBuilder.New().SetText($"out {typeReference}? {value}"));
+ return this;
+ }
+
+ public MethodCallBuilder SetDetermineStatement(bool value)
+ {
+ _determineStatement = value;
+ return this;
+ }
+
+ public MethodCallBuilder SetWrapArguments(bool value = true)
+ {
+ _wrapArguments = value;
+ return this;
+ }
+
+ public MethodCallBuilder SetNullForgiving(bool value = true)
+ {
+ _setNullForgiving = value;
+ return this;
+ }
+
+ public MethodCallBuilder SetReturn(bool value = true)
+ {
+ _setReturn = value;
+ return this;
+ }
+
+ public MethodCallBuilder SetNew(bool value = true)
+ {
+ _setNew = value;
+ return this;
+ }
+
+ public MethodCallBuilder SetAwait(bool value = true)
+ {
+ _setAwait = value;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_determineStatement)
+ {
+ writer.WriteIndent();
+ }
+
+ if (_setReturn)
+ {
+ writer.Write("return ");
+ }
+
+ if (_setNew)
+ {
+ writer.Write("new ");
+ }
+
+ if (_setAwait)
+ {
+ writer.Write("await ");
+ }
+
+ writer.Write(_prefix);
+
+ if (_methodName.Length > 0)
+ {
+ for (var i = 0; i < _methodName.Length - 1; i++)
+ {
+ writer.Write(_methodName[i]);
+ if (i < _methodName.Length - 2)
+ {
+ writer.Write(".");
+ }
+ }
+
+ if (_chainedCode.Count > 0)
+ {
+ writer.WriteLine();
+ writer.IncreaseIndent();
+ writer.WriteIndent();
+ }
+
+ if (_methodName.Length > 1)
+ {
+ writer.Write(".");
+ }
+
+ writer.Write(_methodName[_methodName.Length - 1]);
+
+ if (_generics.Count > 0)
+ {
+ writer.Write("<");
+ for (var i = 0; i < _generics.Count; i++)
+ {
+ _generics[i].Build(writer);
+ if (i == _generics.Count - 1)
+ {
+ writer.Write(">");
+ }
+ else
+ {
+ writer.Write(", ");
+ }
+ }
+ }
+
+ writer.Write("(");
+
+ if (_arguments.Count == 0)
+ {
+ writer.Write(")");
+ if (_setNullForgiving)
+ {
+ writer.Write("!");
+ }
+ }
+ else if (_arguments.Count == 1)
+ {
+ if (_wrapArguments)
+ {
+ writer.WriteLine();
+ writer.IncreaseIndent();
+ writer.WriteIndent();
+ }
+
+ _arguments[0].Build(writer);
+ if (_wrapArguments)
+ {
+ writer.DecreaseIndent();
+ writer.Write(")");
+ }
+ else
+ {
+ writer.Write(")");
+ }
+
+ if (_setNullForgiving)
+ {
+ writer.Write("!");
+ }
+ }
+ else
+ {
+ writer.WriteLine();
+
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _arguments.Count; i++)
+ {
+ writer.WriteIndent();
+ _arguments[i].Build(writer);
+ if (i == _arguments.Count - 1)
+ {
+ writer.Write(")");
+ if (_setNullForgiving)
+ {
+ writer.Write("!");
+ }
+ }
+ else
+ {
+ writer.Write(",");
+ writer.WriteLine();
+ }
+ }
+ }
+ }
+
+ if (_chainedCode.Count > 0)
+ {
+ writer.DecreaseIndent();
+ }
+ }
+
+ using (writer.IncreaseIndent())
+ {
+ foreach (var code in _chainedCode)
+ {
+ writer.WriteLine();
+ writer.WriteIndent();
+ writer.Write('.');
+ code.Build(writer);
+ }
+ }
+
+ if (_determineStatement)
+ {
+ writer.Write(";");
+ writer.WriteLine();
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/NullCheckBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/NullCheckBuilder.cs
new file mode 100644
index 0000000..379626d
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/NullCheckBuilder.cs
@@ -0,0 +1,94 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class NullCheckBuilder : ICode
+{
+ private ICode? _condition;
+ private ICode? _code;
+ private bool _determineStatement = true;
+ private bool _singleLine;
+
+ public NullCheckBuilder SetCondition(ICode condition)
+ {
+ _condition = condition;
+ return this;
+ }
+
+ public NullCheckBuilder SetCondition(string condition)
+ {
+ _condition = CodeInlineBuilder.From(condition);
+ return this;
+ }
+
+ public NullCheckBuilder SetCode(ICode code)
+ {
+ _code = code;
+ return this;
+ }
+
+ public NullCheckBuilder SetCode(string code)
+ {
+ _code = CodeInlineBuilder.From(code);
+ return this;
+ }
+
+ public NullCheckBuilder SetDetermineStatement(bool value)
+ {
+ _determineStatement = value;
+ return this;
+ }
+
+ public NullCheckBuilder SetSingleLine(bool value = true)
+ {
+ _singleLine = value;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (_condition is null)
+ {
+ throw new ArgumentNullException(nameof(_condition));
+ }
+
+ if (_code is null)
+ {
+ throw new ArgumentNullException(nameof(_code));
+ }
+
+ _condition.Build(writer);
+
+ if (!_singleLine)
+ {
+ writer.WriteLine();
+ }
+
+ using (writer.IncreaseIndent())
+ {
+ if (!_singleLine)
+ {
+ writer.WriteIndent();
+ }
+ else
+ {
+ writer.Write(" ");
+ }
+
+ writer.Write("?? ");
+ _code.Build(writer);
+ }
+
+ if (_determineStatement)
+ {
+ writer.Write(";");
+ writer.WriteLine();
+ }
+ }
+
+ public static NullCheckBuilder New() => new();
+
+ public static NullCheckBuilder Inline() => New()
+ .SetDetermineStatement(false)
+ .SetSingleLine();
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ParameterBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ParameterBuilder.cs
new file mode 100644
index 0000000..71a35ab
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/ParameterBuilder.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class ParameterBuilder : ICodeBuilder
+{
+ private TypeReferenceBuilder? _type;
+ private string? _name;
+ private string? _default;
+ private bool _this;
+
+ public static ParameterBuilder New() => new();
+
+ public ParameterBuilder SetType(TypeReferenceBuilder value, bool condition = true)
+ {
+ if (condition)
+ {
+ _type = value;
+ }
+ return this;
+ }
+
+ public ParameterBuilder SetType(string name)
+ {
+ _type = TypeReferenceBuilder.New().SetName(name);
+ return this;
+ }
+
+ public ParameterBuilder SetName(string value)
+ {
+ _name = value;
+ return this;
+ }
+
+ public ParameterBuilder SetThis(bool value = true)
+ {
+ _this = value;
+ return this;
+ }
+
+ public ParameterBuilder SetDefault(string value = "default", bool condition = true)
+ {
+ if (condition)
+ {
+ _default = value;
+ }
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_type is null)
+ {
+ throw new ArgumentNullException(nameof(_type));
+ }
+
+ if (_this)
+ {
+ writer.Write("this ");
+ }
+
+ _type.Build(writer);
+
+ writer.Write(_name);
+
+ if (_default is not null)
+ {
+ writer.Write($" = {_default}");
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/PropertyBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/PropertyBuilder.cs
new file mode 100644
index 0000000..1fbf897
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/PropertyBuilder.cs
@@ -0,0 +1,174 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class PropertyBuilder : ICodeBuilder
+{
+ private AccessModifier _accessModifier;
+ private bool _isReadOnly = true;
+ private ICode? _lambdaResolver;
+ private bool _isOnlyDeclaration;
+ private TypeReferenceBuilder? _type;
+ private string? _name;
+ private XmlCommentBuilder? _xmlComment;
+ private string? _value;
+ private string? _interface;
+ private bool _isStatic;
+ private bool _isOverride;
+
+ public static PropertyBuilder New() => new();
+
+ public PropertyBuilder SetAccessModifier(AccessModifier value)
+ {
+ _accessModifier = value;
+ return this;
+ }
+
+ public PropertyBuilder SetStatic()
+ {
+ _isStatic = true;
+ return this;
+ }
+
+ public PropertyBuilder SetOverride()
+ {
+ _isOverride = true;
+ return this;
+ }
+
+ public PropertyBuilder AsLambda(string resolveCode)
+ {
+ _lambdaResolver = CodeInlineBuilder.From(resolveCode);
+ return this;
+ }
+
+ public PropertyBuilder AsLambda(ICode resolveCode)
+ {
+ _lambdaResolver = resolveCode;
+ return this;
+ }
+
+ public PropertyBuilder SetType(string value)
+ {
+ _type = TypeReferenceBuilder.New().SetName(value);
+ return this;
+ }
+
+ public PropertyBuilder SetComment(string? xmlComment)
+ {
+ if (xmlComment is not null)
+ {
+ _xmlComment = XmlCommentBuilder.New().SetSummary(xmlComment);
+ }
+
+ return this;
+ }
+
+ public PropertyBuilder SetOnlyDeclaration(bool value = true)
+ {
+ _isOnlyDeclaration = value;
+ return this;
+ }
+
+ public PropertyBuilder SetType(TypeReferenceBuilder value)
+ {
+ _type = value;
+ return this;
+ }
+
+ public PropertyBuilder SetName(string value)
+ {
+ _name = value;
+ return this;
+ }
+
+ public PropertyBuilder SetInterface(string value)
+ {
+ _interface = value;
+ return this;
+ }
+
+ public PropertyBuilder SetValue(string? value)
+ {
+ _value = value;
+ return this;
+ }
+
+ public PropertyBuilder MakeSettable()
+ {
+ _isReadOnly = false;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_type is null)
+ {
+ throw new ArgumentNullException(nameof(_type));
+ }
+
+ var modifier = _accessModifier.ToString().ToLowerInvariant();
+
+ _xmlComment?.Build(writer);
+
+ writer.WriteIndent();
+ if (_interface is null && !_isOnlyDeclaration)
+ {
+ writer.Write(modifier);
+ writer.WriteSpace();
+ if (_isStatic)
+ {
+ writer.Write("static");
+ writer.WriteSpace();
+ }
+
+ if (_isOverride)
+ {
+ writer.Write("override");
+ writer.WriteSpace();
+ }
+ }
+
+ _type.Build(writer);
+
+ if (_interface is not null)
+ {
+ writer.Write(_interface);
+ writer.Write(".");
+ }
+
+ writer.Write(_name);
+
+ if (_lambdaResolver is not null)
+ {
+ writer.Write(" => ");
+ _lambdaResolver.Build(writer);
+ writer.Write(";");
+ writer.WriteLine();
+ return;
+ }
+
+ writer.Write(" {");
+ writer.Write(" get;");
+ if (!_isReadOnly)
+ {
+ writer.Write(" set;");
+ }
+
+ writer.Write(" }");
+
+ if (_value is not null)
+ {
+ writer.Write(" = ");
+ writer.Write(_value);
+ writer.Write(";");
+ }
+
+ writer.WriteLine();
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/SwitchExpressionBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/SwitchExpressionBuilder.cs
new file mode 100644
index 0000000..f2c7ce2
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/SwitchExpressionBuilder.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class SwitchExpressionBuilder : ICode
+{
+ private readonly List<(ICode, ICode)> _cases = [];
+ private string? _expression;
+ private bool _determineStatement = true;
+ private bool _setReturn;
+ private string? _prefix;
+ private ICode? _defaultCase;
+
+ public static SwitchExpressionBuilder New() => new();
+
+ public SwitchExpressionBuilder SetPrefix(string prefix)
+ {
+ _prefix = prefix;
+ return this;
+ }
+
+ public SwitchExpressionBuilder SetReturn(bool value = true)
+ {
+ _setReturn = value;
+ return this;
+ }
+
+ public SwitchExpressionBuilder SetExpression(string expression)
+ {
+ _expression = expression;
+ return this;
+ }
+
+ public SwitchExpressionBuilder AddCase(ICode type, ICode action)
+ {
+ _cases.Add((type, action));
+ return this;
+ }
+
+ public SwitchExpressionBuilder AddCase(string type, ICode action)
+ {
+ _cases.Add((CodeInlineBuilder.From(type), action));
+ return this;
+ }
+
+ public SwitchExpressionBuilder AddCase(string type, string action)
+ {
+ return AddCase(CodeInlineBuilder.From(type), CodeInlineBuilder.From(action));
+ }
+
+ public SwitchExpressionBuilder SetDefaultCase(string action)
+ {
+ _defaultCase = CodeInlineBuilder.From(action);
+ return this;
+ }
+
+ public SwitchExpressionBuilder SetDefaultCase(ICode action)
+ {
+ _defaultCase = action;
+ return this;
+ }
+
+ public SwitchExpressionBuilder SetDetermineStatement(bool value)
+ {
+ _determineStatement = value;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_determineStatement)
+ {
+ writer.WriteIndent();
+ }
+
+ if (_setReturn)
+ {
+ writer.Write("return ");
+ }
+
+ writer.Write(_prefix);
+
+ writer.Write(_expression);
+
+ writer.Write(" switch");
+ writer.WriteLine();
+
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _cases.Count; i++)
+ {
+ var (type, action) = _cases[i];
+
+ writer.WriteIndent();
+ type.Build(writer);
+ writer.Write(" => ");
+ action.Build(writer);
+
+ if (i < _cases.Count - 1 || _defaultCase is not null)
+ {
+ writer.Write(",");
+ }
+
+ writer.WriteLine();
+ }
+
+ if (_defaultCase is not null)
+ {
+ writer.WriteIndent();
+ writer.Write("_ => ");
+ _defaultCase.Build(writer);
+ writer.WriteLine();
+ }
+ }
+
+ writer.WriteIndent();
+ writer.Write("}");
+
+ if (_determineStatement)
+ {
+ writer.Write(";");
+ writer.WriteLine();
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TryCatchBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TryCatchBuilder.cs
new file mode 100644
index 0000000..ad1be3c
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TryCatchBuilder.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class TryCatchBuilder : ICode
+{
+ private readonly List _try = [];
+ private readonly List _catch = [];
+
+ public static TryCatchBuilder New() => new();
+
+ public TryCatchBuilder AddTryCode(ICode code)
+ {
+ _try.Add(code);
+ return this;
+ }
+
+ public TryCatchBuilder AddCatchBlock(CatchBlockBuilder code)
+ {
+ _catch.Add(code);
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (_catch.Count == 0 || _try.Count == 0)
+ {
+ throw new InvalidOperationException(
+ "The catch build needs at least one try and one catch.");
+ }
+
+ writer.WriteIndentedLine("try");
+ writer.WriteIndentedLine("{");
+
+ using (writer.IncreaseIndent())
+ {
+ foreach (var code in _try)
+ {
+ code.Build(writer);
+ }
+ }
+
+ writer.WriteIndentedLine("}");
+
+ foreach (var code in _catch)
+ {
+ code.Build(writer);
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TupleBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TupleBuilder.cs
new file mode 100644
index 0000000..95e05fe
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TupleBuilder.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class TupleBuilder : ICode
+{
+ private bool _determineStatement = false;
+ private string? _prefix;
+ private bool _setReturn;
+ private readonly List _members = [];
+
+ public static TupleBuilder New() => new();
+
+ public static TupleBuilder Inline() => New().SetDetermineStatement(false);
+
+ public TupleBuilder AddMember(string value)
+ {
+ _members.Add(CodeInlineBuilder.New().SetText(value));
+ return this;
+ }
+
+ public TupleBuilder AddMember(ICode value)
+ {
+ _members.Add(value);
+ return this;
+ }
+
+ public TupleBuilder SetPrefix(string prefix)
+ {
+ _prefix = prefix;
+ return this;
+ }
+
+ public TupleBuilder SetDetermineStatement(bool value)
+ {
+ _determineStatement = value;
+ return this;
+ }
+
+ public TupleBuilder SetReturn(bool value = true)
+ {
+ _setReturn = value;
+ return this;
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ if (_determineStatement)
+ {
+ writer.WriteIndent();
+ }
+
+ if (_setReturn)
+ {
+ writer.Write("return ");
+ }
+
+ writer.Write(_prefix);
+
+ writer.Write("(");
+
+ if (_members.Count == 0)
+ {
+ writer.Write(")");
+ }
+ else if (_members.Count == 1)
+ {
+ _members[0].Build(writer);
+ writer.Write(")");
+ }
+ else
+ {
+ writer.WriteLine();
+
+ using (writer.IncreaseIndent())
+ {
+ for (var i = 0; i < _members.Count; i++)
+ {
+ writer.WriteIndent();
+ _members[i].Build(writer);
+ if (i != _members.Count - 1)
+ {
+ writer.Write(",");
+ }
+
+ writer.WriteLine();
+ }
+ }
+
+ writer.WriteIndent();
+ writer.Write(")");
+ }
+
+ if (_determineStatement)
+ {
+ writer.Write(";");
+ writer.WriteLine();
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TupleBuilderExtensions.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TupleBuilderExtensions.cs
new file mode 100644
index 0000000..1c7f1c8
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TupleBuilderExtensions.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public static class TupleBuilderExtensions
+{
+ public static TupleBuilder AddMemberRange(
+ this TupleBuilder builder,
+ IEnumerable range)
+ {
+ foreach (var member in range)
+ {
+ builder.AddMember(member);
+ }
+
+ return builder;
+ }
+
+ public static TupleBuilder AddMemberRange(
+ this TupleBuilder builder,
+ IEnumerable range)
+ {
+ foreach (var member in range)
+ {
+ builder.AddMember(member);
+ }
+
+ return builder;
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TypeReferenceBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TypeReferenceBuilder.cs
new file mode 100644
index 0000000..a8ed208
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/TypeReferenceBuilder.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using HotChocolate.Language;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class TypeReferenceBuilder : ICode
+{
+ private readonly List _buildOrder = [];
+ private string? _name;
+ private readonly List _genericTypeArguments = [];
+ private bool _skipTrailingSpace;
+
+ public static TypeReferenceBuilder New()
+ {
+ return new();
+ }
+
+ public TypeReferenceBuilder SetName(string name)
+ {
+ _name = name;
+ return this;
+ }
+
+ public TypeReferenceBuilder SkipTrailingSpace()
+ {
+ _skipTrailingSpace = true;
+ return this;
+ }
+
+ public TypeReferenceBuilder SetNameSpace(string @namespace)
+ {
+ return this;
+ }
+
+ public TypeReferenceBuilder SetListType()
+ {
+ _buildOrder.Push(TypeKindToken.List);
+ return this;
+ }
+
+ public TypeReferenceBuilder AddGeneric(string name)
+ {
+ _genericTypeArguments.Push(name);
+ return this;
+ }
+
+ public TypeReferenceBuilder SetIsNullable(bool isNullable)
+ {
+ if (isNullable)
+ {
+ _buildOrder.Push(TypeKindToken.Nullable);
+ }
+ return this;
+ }
+
+ private enum TypeKindToken
+ {
+ List,
+ Nullable,
+ }
+
+ public override string ToString()
+ {
+ var text = new StringBuilder();
+ using var stringWriter = new StringWriter(text);
+ using var codeWriter = new CodeWriter(stringWriter);
+ Build(codeWriter);
+ codeWriter.Flush();
+ stringWriter.Flush();
+ return text.ToString();
+ }
+
+ public void Build(CodeWriter writer)
+ {
+ HandleQueue(writer, 0);
+ if (!_skipTrailingSpace)
+ {
+ writer.WriteSpace();
+ }
+ }
+
+ private void HandleQueue(CodeWriter writer, int currentIndex)
+ {
+ if (currentIndex >= _buildOrder.Count)
+ {
+ writer.Write(_name);
+ if (_genericTypeArguments.Count > 0)
+ {
+ writer.Write("<");
+ var next = false;
+ foreach (var generic in _genericTypeArguments)
+ {
+ if (next)
+ {
+ writer.Write(", ");
+ }
+ next = true;
+
+ writer.Write(generic);
+ }
+ writer.Write(">");
+ }
+
+ return;
+ }
+
+ var token = _buildOrder[currentIndex];
+ switch (token)
+ {
+ case TypeKindToken.List:
+ writer.Write(TypeNames.GenericCollectionsNamespace + "IReadOnlyList<");
+ HandleQueue(writer, currentIndex + 1);
+ writer.Write(">");
+ break;
+ case TypeKindToken.Nullable:
+ HandleQueue(writer, currentIndex + 1);
+ writer.Write("?");
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Builders/XmlCommentBuilder.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/XmlCommentBuilder.cs
new file mode 100644
index 0000000..6def21e
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Builders/XmlCommentBuilder.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace StrawberryShake.CodeGeneration.CSharp.Builders;
+
+public class XmlCommentBuilder : ICodeBuilder
+{
+ private string? _summary;
+ private List _code = [];
+
+ public XmlCommentBuilder SetSummary(string summary)
+ {
+ _summary = summary;
+ return this;
+ }
+
+ public XmlCommentBuilder AddCode(string code)
+ {
+ _code.Add(code);
+ return this;
+ }
+
+ public static XmlCommentBuilder New() => new();
+
+ public void Build(CodeWriter writer)
+ {
+ if (_summary is not null)
+ {
+ writer.WriteIndentedLine("/// ");
+ WriteCommentLines(writer, _summary);
+
+ foreach (var code in _code)
+ {
+ writer.WriteIndentedLine("/// ");
+ WriteCommentLines(writer, code);
+ writer.WriteIndentedLine("///
");
+ }
+
+ writer.WriteIndentedLine("/// ");
+ }
+ }
+
+ private void WriteCommentLines(CodeWriter writer, string str)
+ {
+ using var reader = new StringReader(str);
+ var line = reader.ReadLine();
+ do
+ {
+ if (line is not null)
+ {
+ writer.WriteIndent();
+ writer.Write("/// ");
+ writer.Write(line);
+ writer.WriteLine();
+ }
+
+ line = reader.ReadLine();
+ }
+ while (line is not null);
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/CodeGeneratorException.cs b/DragonFruit.Data.Roslyn/CodeGeneration/CodeGeneratorException.cs
new file mode 100644
index 0000000..3883d4a
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/CodeGeneratorException.cs
@@ -0,0 +1,5 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration;
+
+public class CodeGeneratorException(string message) : Exception(message);
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/CodeWriter.cs b/DragonFruit.Data.Roslyn/CodeGeneration/CodeWriter.cs
new file mode 100644
index 0000000..d20d80e
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/CodeWriter.cs
@@ -0,0 +1,144 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace StrawberryShake.CodeGeneration;
+
+public class CodeWriter : TextWriter
+{
+ private readonly TextWriter _writer;
+ private readonly bool _disposeWriter;
+ private bool _disposed;
+ private int _indent;
+
+ public CodeWriter(TextWriter writer)
+ {
+ _writer = writer;
+ _disposeWriter = false;
+ }
+
+ public CodeWriter(StringBuilder text)
+ {
+ _writer = new StringWriter(text);
+ _disposeWriter = true;
+ }
+
+ public override Encoding Encoding { get; } = Encoding.UTF8;
+
+ public static string Indent { get; } = new(' ', 4);
+
+ public override void Write(char value) =>
+ _writer.Write(value);
+
+ public void WriteStringValue(string value)
+ {
+ Write('"');
+ Write(value);
+ Write('"');
+ }
+
+ public void WriteIndent()
+ {
+ if (_indent > 0)
+ {
+ var spaces = _indent * 4;
+ for (var i = 0; i < spaces; i++)
+ {
+ Write(' ');
+ }
+ }
+ }
+
+ public string GetIndentString()
+ {
+ if (_indent > 0)
+ {
+ return new string(' ', _indent * 4);
+ }
+ return string.Empty;
+ }
+
+ public void WriteIndentedLine(string format, params object?[] args)
+ {
+ WriteIndent();
+
+ if (args.Length == 0)
+ {
+ Write(format);
+ }
+ else
+ {
+ Write(format, args);
+ }
+
+ WriteLine();
+ }
+
+ public void WriteSpace() => Write(' ');
+
+ public IDisposable IncreaseIndent()
+ {
+ _indent++;
+ return new Block(DecreaseIndent);
+ }
+
+ public void DecreaseIndent()
+ {
+ if (_indent > 0)
+ {
+ _indent--;
+ }
+ }
+
+ public IDisposable WriteBraces()
+ {
+ WriteLeftBrace();
+ WriteLine();
+
+#pragma warning disable CA2000
+ var indent = IncreaseIndent();
+#pragma warning restore CA2000
+
+ return new Block(() =>
+ {
+ WriteLine();
+ indent.Dispose();
+ WriteIndent();
+ WriteRightBrace();
+ });
+ }
+
+ public void WriteLeftBrace() => Write('{');
+
+ public void WriteRightBrace() => Write('}');
+
+ public override void Flush()
+ {
+ base.Flush();
+ _writer.Flush();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposed && _disposeWriter)
+ {
+ if (disposing)
+ {
+ _writer.Dispose();
+ }
+ _disposed = true;
+ }
+ }
+
+ private sealed class Block : IDisposable
+ {
+ private readonly Action _decrease;
+
+ public Block(Action close)
+ {
+ _decrease = close;
+ }
+
+ public void Dispose() => _decrease();
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/CodeWriterExtensions.cs b/DragonFruit.Data.Roslyn/CodeGeneration/CodeWriterExtensions.cs
new file mode 100644
index 0000000..55646c6
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/CodeWriterExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace StrawberryShake.CodeGeneration;
+
+public static class CodeWriterExtensions
+{
+ public static void WriteGeneratedAttribute(this CodeWriter writer)
+ {
+ if (writer is null)
+ {
+ throw new ArgumentNullException(nameof(writer));
+ }
+
+ var version = typeof(CodeWriter).Assembly.GetName().Version!.ToString();
+
+#if DEBUG
+ writer.WriteIndentedLine(
+ "[global::System.CodeDom.Compiler.GeneratedCode(" +
+ "\"DragonFruit.Data\", \"4.0.0\")]");
+#else
+ writer.WriteIndentedLine(
+ "[global::System.CodeDom.Compiler.GeneratedCode(" +
+ $"\"DragonFruit.Data\", \"{version}\")]");
+#endif
+ }
+
+ public static CodeWriter WriteComment(this CodeWriter writer, string comment)
+ {
+ writer.Write("// ");
+ writer.WriteLine(comment);
+ return writer;
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/Keywords.cs b/DragonFruit.Data.Roslyn/CodeGeneration/Keywords.cs
new file mode 100644
index 0000000..9d1c78c
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/Keywords.cs
@@ -0,0 +1,142 @@
+using System.Collections.Generic;
+
+namespace StrawberryShake.CodeGeneration.CSharp;
+
+public static class Keywords
+{
+ private static readonly HashSet _keywords =
+ [
+ "abstract",
+ "as",
+ "base",
+ "bool",
+ "break",
+ "byte",
+ "case",
+ "catch",
+ "char",
+ "checked",
+ "class",
+ "const",
+ "continue",
+ "decimal",
+ "default",
+ "delegate",
+ "do",
+ "double",
+ "else",
+ "enum",
+ "event",
+ "explicit",
+ "extern",
+ "false",
+ "finally",
+ "fixed",
+ "float",
+ "for",
+ "foreach",
+ "goto",
+ "if",
+ "implicit",
+ "in",
+ "int",
+ "interface",
+ "internal",
+ "is",
+ "lock",
+ "long",
+ "namespace",
+ "new",
+ "null",
+ "object",
+ "operator",
+ "out",
+ "override",
+ "params",
+ "private",
+ "protected",
+ "public",
+ "readonly",
+ "record",
+ "ref",
+ "return",
+ "sbyte",
+ "sealed",
+ "short",
+ "sizeof",
+ "stackalloc",
+ "static",
+ "string",
+ "struct",
+ "switch",
+ "this",
+ "throw",
+ "true",
+ "try",
+ "typeof",
+ "uint",
+ "ulong",
+ "unchecked",
+ "unsafe",
+ "ushort",
+ "using",
+ "virtual",
+ "void",
+ "volatile",
+ "while",
+ "add",
+ "alias",
+ "ascending",
+ "async",
+ "await",
+ "by",
+ "descending",
+ "dynamic",
+ "equals",
+ "from",
+ "get",
+ "global",
+ "group",
+ "init",
+ "into",
+ "join",
+ "let",
+ "nameof",
+ "nint",
+ "notnull",
+ "nuint",
+ "on",
+ "orderby",
+ "partial",
+ "remove",
+ "select",
+ "set",
+ "unmanaged",
+ "value",
+ "var",
+ "when",
+ "where",
+ "with",
+ "yield",
+ ];
+
+ public static string ToSafeName(string name)
+ {
+ if (_keywords.Contains(name))
+ {
+ return $"@{name}";
+ }
+
+ return name;
+ }
+
+ public static string ToEscapedName(this string name)
+ {
+ if (_keywords.Contains(name))
+ {
+ return $"@{name}";
+ }
+
+ return name;
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/ListExtensions.cs b/DragonFruit.Data.Roslyn/CodeGeneration/ListExtensions.cs
new file mode 100644
index 0000000..921848b
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/ListExtensions.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+
+namespace HotChocolate.Language;
+
+///
+/// Provides Stack and Queue extensions for to
+/// the visitor APIs.
+///
+public static class ListExtensions
+{
+ public static T Pop(this IList list)
+ {
+ var lastIndex = list.Count - 1;
+ var p = list[lastIndex];
+ list.RemoveAt(lastIndex);
+ return p;
+ }
+
+ public static bool TryPop(this IList list, out T item)
+ {
+ if (list.Count > 0)
+ {
+ var lastIndex = list.Count - 1;
+ item = list[lastIndex]!;
+ list.RemoveAt(lastIndex);
+ return true;
+ }
+ else
+ {
+ item = default!;
+ return false;
+ }
+ }
+
+ public static T Peek(this IList list)
+ {
+ var lastIndex = list.Count - 1;
+ return list[lastIndex];
+ }
+
+ public static bool TryPeek(this IList list, out T item)
+ {
+ if (list.Count > 0)
+ {
+ var lastIndex = list.Count - 1;
+ item = list[lastIndex]!;
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public static bool TryPeek(
+ this IList list,
+ int elements,
+ out T item)
+ {
+ if (list.Count >= elements)
+ {
+ var lastIndex = list.Count - elements;
+ item = list[lastIndex]!;
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public static T? PeekOrDefault(this IList list, T? defaultValue = default)
+ {
+ if (list.Count > 0)
+ {
+ var lastIndex = list.Count - 1;
+ return list[lastIndex];
+ }
+
+ return defaultValue;
+ }
+
+ public static TSearch? PeekOrDefault(this IList list, TSearch? defaultValue = default)
+ {
+ if (list.Count > 0)
+ {
+ for (var i = list.Count - 1; i >= 0; i--)
+ {
+ if (list[i] is TSearch item)
+ {
+ return item;
+ }
+ }
+ }
+
+ return defaultValue;
+ }
+
+ public static void Push(this IList list, T item)
+ {
+ list.Add(item);
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/RuntimeTypeInfo.cs b/DragonFruit.Data.Roslyn/CodeGeneration/RuntimeTypeInfo.cs
new file mode 100644
index 0000000..2b108be
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/RuntimeTypeInfo.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Linq;
+using StrawberryShake.CodeGeneration.CSharp;
+
+namespace StrawberryShake.CodeGeneration;
+
+public class RuntimeTypeInfo : IEquatable
+{
+ public RuntimeTypeInfo(string fullName, bool isValueType = false)
+ {
+ var parts = fullName.Split('.');
+ Name = parts.Last();
+ Namespace = string.Join(".", parts.Take(parts.Length - 1));
+ IsValueType = isValueType;
+
+ if (!Namespace.StartsWith("global::"))
+ {
+ Namespace = "global::" + Namespace;
+ }
+ }
+
+ public RuntimeTypeInfo(string name, string @namespace, bool isValueType = false)
+ {
+ Name = name;
+ Namespace = @namespace;
+ IsValueType = isValueType;
+
+ if (!Namespace.StartsWith("global::"))
+ {
+ Namespace = "global::" + Namespace;
+ }
+ }
+
+ public string Name { get; }
+
+ public string Namespace { get; }
+
+ public string FullName =>
+ Namespace == "global::"
+ ? Namespace + Name.ToEscapedName()
+ : Namespace + "." + Name.ToEscapedName();
+
+ public string NamespaceWithoutGlobal =>
+ Namespace.Replace("global::", string.Empty);
+
+ public bool IsValueType { get; }
+
+ public bool Equals(RuntimeTypeInfo? other)
+ {
+ if (ReferenceEquals(null, other))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return Name == other.Name &&
+ Namespace == other.Namespace &&
+ IsValueType == other.IsValueType;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != GetType())
+ {
+ return false;
+ }
+
+ return Equals((RuntimeTypeInfo)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = Name.GetHashCode();
+ hashCode = (hashCode * 397) ^ Namespace.GetHashCode();
+ hashCode = (hashCode * 397) ^ IsValueType.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public override string ToString()
+ {
+ return $"{Namespace}.{Name.ToEscapedName()}";
+ }
+}
diff --git a/DragonFruit.Data.Roslyn/CodeGeneration/TypeNames.cs b/DragonFruit.Data.Roslyn/CodeGeneration/TypeNames.cs
new file mode 100644
index 0000000..1aba612
--- /dev/null
+++ b/DragonFruit.Data.Roslyn/CodeGeneration/TypeNames.cs
@@ -0,0 +1,65 @@
+// ReSharper disable InconsistentNaming
+
+namespace StrawberryShake.CodeGeneration;
+
+public static class TypeNames
+{
+ public const string IEquatable = "global::System.IEquatable";
+ public const string Type = "global::System.Type";
+ public const string Nullable = "global::System.Nullable";
+ public const string JsonElement = "global::System.Text.Json.JsonElement";
+ public const string JsonDocument = "global::System.Text.Json.JsonDocument";
+ public const string JsonValueKind = "global::System.Text.Json.JsonValueKind";
+ public const string JsonWriterOptions = "global::System.Text.Json.JsonWriterOptions";
+ public const string Utf8JsonWriter = "global::System.Text.Json.Utf8JsonWriter";
+
+ public const string String = "global::System.String";
+ public const string Byte = "global::System.Byte";
+ public const string ByteArray = "global::System.Byte[]";
+ public const string Array = "global::System.Array";
+ public const string Int16 = "global::System.Int16";
+ public const string Int32 = "global::System.Int32";
+ public const string Int64 = "global::System.Int64";
+ public const string UInt16 = "global::System.UInt16";
+ public const string UInt32 = "global::System.UInt32";
+ public const string UInt64 = "global::System.UInt64";
+ public const string Single = "global::System.Single";
+ public const string Double = "global::System.Double";
+ public const string Decimal = "global::System.Decimal";
+ public const string Uri = "global::System.Uri";
+ public const string Boolean = "global::System.Boolean";
+ public const string Object = "global::System.Object";
+ public const string Guid = "global::System.Guid";
+ public const string DateTime = "global::System.DateTime";
+ public const string TimeSpan = "global::System.TimeSpan";
+ public const string EncodingUtf8 = "global::System.Text.Encoding.UTF8";
+ public const string List = GenericCollectionsNamespace + "List";
+ public const string IEnumerable = GenericCollectionsNamespace + "IEnumerable";
+ public const string Concat = "global::System.Linq.Enumerable.Concat";
+ public const string IList = GenericCollectionsNamespace + "IList";
+
+ public const string IReadOnlyCollection = GenericCollectionsNamespace + "IReadOnlyCollection";
+
+ public const string IReadOnlyDictionary = GenericCollectionsNamespace + "IReadOnlyDictionary";
+
+ public const string IReadOnlyList = GenericCollectionsNamespace + "IReadOnlyList";
+ public const string HashSet = GenericCollectionsNamespace + "HashSet";
+ public const string ISet = GenericCollectionsNamespace + "ISet";
+ public const string IReadOnlySpan = "global::System.ReadOnlySpan";
+ public const string DateTimeOffset = "global::System.DateTimeOffset";
+ public const string OrdinalStringComparison = "global::System.StringComparison.Ordinal";
+ public const string Func = "global::System.Func";
+ public const string Task = "global::System.Threading.Tasks.Task";
+ public const string CancellationToken = "global::System.Threading.CancellationToken";
+ public const string NotSupportedException = "global::System.NotSupportedException";
+ public const string ArgumentNullException = "global::System.ArgumentNullException";
+ public const string ArgumentException = "global::System.ArgumentException";
+
+ public const string ArgumentOutOfRangeException = "global::System.ArgumentOutOfRangeException";
+
+ public const string Exception = "global::System.Exception";
+
+ public const string GenericCollectionsNamespace = "global::System.Collections.Generic.";
+ public const string Dictionary = "global::System.Collections.Generic.Dictionary";
+ public const string KeyValuePair = "global::System.Collections.Generic.KeyValuePair";
+}
diff --git a/DragonFruit.Data.Roslyn/DragonFruit.Data.Roslyn.csproj b/DragonFruit.Data.Roslyn/DragonFruit.Data.Roslyn.csproj
index 6849f8c..e9738b5 100644
--- a/DragonFruit.Data.Roslyn/DragonFruit.Data.Roslyn.csproj
+++ b/DragonFruit.Data.Roslyn/DragonFruit.Data.Roslyn.csproj
@@ -10,6 +10,7 @@
DragonFruit.Data.Roslyn
DragonFruit.Data.Roslyn
+ CS8669,$(NoWarn)
false
true
false
@@ -30,16 +31,10 @@
-
-
-
-
-
-
@@ -51,9 +46,8 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
-
+
\ No newline at end of file
diff --git a/DragonFruit.Data.Roslyn/Entities/EnumSymbolMetadata.cs b/DragonFruit.Data.Roslyn/Entities/EnumSymbolMetadata.cs
index 62c8d44..b6ced13 100644
--- a/DragonFruit.Data.Roslyn/Entities/EnumSymbolMetadata.cs
+++ b/DragonFruit.Data.Roslyn/Entities/EnumSymbolMetadata.cs
@@ -7,7 +7,7 @@
namespace DragonFruit.Data.Roslyn.Entities
{
- internal class EnumSymbolMetadata : PropertySymbolMetadata
+ internal class EnumSymbolMetadata : ParameterSymbolMetadata
{
public override RequestSymbolType Type => RequestSymbolType.Enum;
diff --git a/DragonFruit.Data.Roslyn/Entities/EnumerableSymbolMetadata.cs b/DragonFruit.Data.Roslyn/Entities/EnumerableSymbolMetadata.cs
index a8d5808..c543e03 100644
--- a/DragonFruit.Data.Roslyn/Entities/EnumerableSymbolMetadata.cs
+++ b/DragonFruit.Data.Roslyn/Entities/EnumerableSymbolMetadata.cs
@@ -7,7 +7,7 @@
namespace DragonFruit.Data.Roslyn.Entities
{
- internal class EnumerableSymbolMetadata : PropertySymbolMetadata
+ internal class EnumerableSymbolMetadata : ParameterSymbolMetadata
{
public override RequestSymbolType Type => RequestSymbolType.Enumerable;
diff --git a/DragonFruit.Data.Roslyn/Entities/KeyValuePairSymbolMetadata.cs b/DragonFruit.Data.Roslyn/Entities/KeyValuePairSymbolMetadata.cs
index 546ec95..72a8d2a 100644
--- a/DragonFruit.Data.Roslyn/Entities/KeyValuePairSymbolMetadata.cs
+++ b/DragonFruit.Data.Roslyn/Entities/KeyValuePairSymbolMetadata.cs
@@ -6,7 +6,7 @@
namespace DragonFruit.Data.Roslyn.Entities
{
- internal class KeyValuePairSymbolMetadata : PropertySymbolMetadata
+ internal class KeyValuePairSymbolMetadata : ParameterSymbolMetadata
{
public override RequestSymbolType Type => RequestSymbolType.KeyValuePair;
diff --git a/DragonFruit.Data.Roslyn/Entities/PropertySymbolMetadata.cs b/DragonFruit.Data.Roslyn/Entities/ParameterSymbolMetadata.cs
similarity index 70%
rename from DragonFruit.Data.Roslyn/Entities/PropertySymbolMetadata.cs
rename to DragonFruit.Data.Roslyn/Entities/ParameterSymbolMetadata.cs
index b9a5b03..5d5a5a6 100644
--- a/DragonFruit.Data.Roslyn/Entities/PropertySymbolMetadata.cs
+++ b/DragonFruit.Data.Roslyn/Entities/ParameterSymbolMetadata.cs
@@ -6,11 +6,11 @@
namespace DragonFruit.Data.Roslyn.Entities
{
- internal class PropertySymbolMetadata : SymbolMetadata
+ internal class ParameterSymbolMetadata : SymbolMetadata
{
public virtual RequestSymbolType Type { get; }
- public PropertySymbolMetadata(ISymbol symbol, ITypeSymbol returnType, string parameterName, RequestSymbolType type = RequestSymbolType.Standard)
+ public ParameterSymbolMetadata(ISymbol symbol, ITypeSymbol returnType, string parameterName, RequestSymbolType type = RequestSymbolType.Standard)
: base(symbol, returnType)
{
Type = type;
diff --git a/DragonFruit.Data.Roslyn/Templates/ApiRequest.liquid b/DragonFruit.Data.Roslyn/Templates/ApiRequest.liquid
deleted file mode 100644
index 937bfe3..0000000
--- a/DragonFruit.Data.Roslyn/Templates/ApiRequest.liquid
+++ /dev/null
@@ -1,175 +0,0 @@
-using System;
-using System.Text;
-using System.Net.Http;
-using DragonFruit.Data;
-using DragonFruit.Data.Requests;
-
-namespace {{ namespace }}
-{
- partial class {{ class_name }} : global::DragonFruit.Data.Requests.IRequestBuilder
- {
- public {% if require_new_keyword %}new{% endif %} global::System.Net.Http.HttpRequestMessage BuildRequest(global::DragonFruit.Data.Serializers.SerializerResolver serializerResolver)
- {
- global::System.UriBuilder uriBuilder = new global::System.UriBuilder(this.RequestPath);
-
- {% comment %} Process Query Parameters {% endcomment %}
- {% if query_parameters.size > 0 -%}
- global::System.Text.StringBuilder queryBuilder = new global::System.Text.StringBuilder();
-
- {% for query in query_parameters -%}
- {% capture query_append -%}
- {% case query.type -%}
- {% when 1 -%}
- global::DragonFruit.Data.Converters.EnumerableConverter.WriteEnumerable(queryBuilder, {{ query.accessor }}, global::DragonFruit.Data.Requests.EnumerableOption.{{ query.enumerable_option }}, "{{ query.parameter_name }}", "{{ query.separator }}");
- {% when 2 -%}
- global::DragonFruit.Data.Converters.EnumConverter.WriteEnum(queryBuilder, {{ query.accessor }}, global::DragonFruit.Data.Requests.EnumOption.{{ query.enum_option }}, "{{ query.parameter_name }}");
- {% when 3 -%}
- global::DragonFruit.Data.Converters.KeyValuePairConverter.WriteKeyValuePairs(queryBuilder, {{ query.accessor }});
- {% else -%}
- queryBuilder.AppendFormat("{0}={1}&", "{{ query.parameter_name }}", global::System.Uri.EscapeDataString({{ query.accessor }}.ToString()));
- {% endcase -%}
- {% endcapture -%}
- {% if query.nullable %}
- if ({{ query.accessor }} != null)
- {
- {{ query_append }}
- }
- {% else -%}
- {{ query_append }}
- {% endif -%}
- {% endfor -%}
-
- {% comment %} remove trailing & {% endcomment %}
- if (queryBuilder.Length > 0)
- {
- queryBuilder.Length--;
- uriBuilder.Query = queryBuilder.ToString();
- }
- {% endif %}
-
- global::System.Net.Http.HttpRequestMessage request = new global::System.Net.Http.HttpRequestMessage(this.RequestMethod, uriBuilder.Uri);
-
- {% case request_body_type -%}
- {% comment %} 1 - Multipart {% endcomment %}
- {% when 1 -%}
- global::System.Net.Http.MultipartFormDataContent content = new global::System.Net.Http.MultipartFormDataContent();
-
- {% for multipart in form_body_parameters -%}
- {% capture multipart_append -%}
- {% case multipart.type -%}
- {% when 1 -%}
- foreach (var kvp in global::DragonFruit.Data.Converters.EnumerableConverter.GetPairs({{ multipart.accessor }}, global::DragonFruit.Data.Requests.EnumerableOption.{{ multipart.enumerable_option }}))
- {
- content.Add(new global::System.Net.Http.StringContent(kvp.Value), "{{ multipart.parameter_name }}");
- }
- {% when 2 -%}
- content.Add(new global::System.Net.Http.StringContent(global::DragonFruit.Data.Converters.EnumConverter.GetEnumValue({{ multipart.accessor }}, global::DragonFruit.Data.Requests.EnumOption.{{ multipart.enum_option }})), "{{ multipart.parameter_name }}");
- {% when 3 %}
- foreach (var kvp in (global::System.Collections.Generic.IEnumerable>){{ multipart.accessor }})
- {
- content.Add(new global::System.Net.Http.StringContent(kvp.Value), kvp.Key);
- }
- {% comment %} 4 - Stream {% endcomment %}
- {% when 4 -%}
- content.Add(new global::System.Net.Http.StreamContent({{ multipart.accessor }}), "{{ multipart.parameter_name }}");
- {% comment %} 5 - ByteArray {% endcomment %}
- {% when 5 -%}
- content.Add(new global::System.Net.Http.ByteArrayContent({{ multipart.accessor }}), "{{ multipart.parameter_name }}");
- {% comment %} Handle other types using ToString {% endcomment %}
- {% else -%}
- content.Add(new global::System.Net.Http.StringContent({{ multipart.accessor }}.ToString()), "{{ multipart.parameter_name }}");
- {% endcase -%}
- {% endcapture -%}
-
- {% if multipart.nullable %}
- if ({{ multipart.accessor }} != null)
- {
- {{ multipart_append }}
- }
- {% else -%}
- {{ multipart_append }}
- {% endif -%}
- {% endfor -%}
-
- request.Content = content;
-
- {% comment %} 2 - UriEncoded {% endcomment %}
- {% when 2 -%}
- global::System.Text.StringBuilder formBuilder = new global::System.Text.StringBuilder();
-
- {% for uriparam in form_body_parameters -%}
- {% capture uriparam_append -%}
- {% case uriparam.type -%}
- {% when 1 -%}
- global::DragonFruit.Data.Converters.EnumerableConverter.WriteEnumerable(formBuilder, {{ uriparam.accessor }}, global::DragonFruit.Data.Requests.EnumerableOption.{{ uriparam.enumerable_option }}, "{{ uriparam.parameter_name }}", "{{ uriparam.separator }}");
- {% when 2 -%}
- global::DragonFruit.Data.Converters.EnumConverter.WriteEnum(formBuilder, {{ uriparam.accessor }}, global::DragonFruit.Data.Requests.EnumOption.{{ uriparam.enum_option }}, "{{ uriparam.parameter_name }}");
- {% when 3 -%}
- global::DragonFruit.Data.Converters.KeyValuePairConverter.WriteKeyValuePairs(formBuilder, {{ uriparam.accessor }});
- {% else -%}
- formBuilder.AppendFormat("{0}={1}&", "{{ uriparam.parameter_name }}", global::System.Uri.EscapeDataString({{ uriparam.accessor }}.ToString()));
- {% endcase -%}
- {% endcapture -%}
-
- {% if uriparam.nullable %}
- if ({{ uriparam.accessor }} != null)
- {
- {{ uriparam_append }}
- }
- {% else -%}
- {{ uriparam_append }}
- {% endif -%}
- {% endfor -%}
-
- {% comment %} remove trailing & {% endcomment %}
- if (formBuilder.Length > 0)
- {
- formBuilder.Length--;
- }
-
- request.Content = new global::System.Net.Http.StringContent(formBuilder.ToString(), global::System.Text.Encoding.UTF8, "application/x-www-form-urlencoded");
-
- {% comment %} 3 - Custom Body (HttpContent) {% endcomment %}
- {% when 3 -%}
- request.Content = {{ request_body_symbol.accessor }};
-
- {% comment %} 4 - Custom Body (Serialized) {% endcomment %}
- {% when 4 -%}
- request.Content = serializerResolver.Resolve({{ request_body_symbol.accessor }}.GetType(), global::DragonFruit.Data.Serializers.DataDirection.Out).Serialize({{ request_body_symbol.accessor }});
- {% endcase -%}
-
- {% comment %} Process Headers {% endcomment %}
- {% for header in header_parameters -%}
- {% capture header_append -%}
- {% case header.type -%}
- {% when 1 -%}
- foreach (var kvp in global::DragonFruit.Data.Converters.EnumerableConverter.GetPairs({{ header.accessor }}, global::DragonFruit.Data.Requests.EnumerableOption.{{ header.enumerable_option }}, "{{ header.parameter_name }}", "{{ header.separator }}"))
- {
- request.Headers.Add(kvp.Key, kvp.Value);
- }
- {% when 2 -%}
- request.Headers.Add("{{ header.parameter_name }}", global::DragonFruit.Data.Converters.EnumConverter.GetEnumValue({{ header.accessor }}, global::DragonFruit.Data.Requests.EnumOption.{{ header.enum_option }}));
- {% when 3 -%}
- foreach (var kvp in (global::System.Collections.Generic.IEnumerable>){{ header.accessor }})
- {
- request.Headers.Add(kvp.Key, kvp.Value);
- }
- {% else -%}
- request.Headers.Add("{{ header.parameter_name }}", {{ header.accessor }}.ToString());
- {% endcase -%}
- {% endcapture -%}
-
- {% if header.nullable -%}
- if ({{ header.accessor }} != null)
- {
- {{ header_append }}
- }
- {% else -%}
- {{ header_append }}
- {% endif -%}
- {% endfor -%}
-
- return request;
- }
- }
-}
diff --git a/DragonFruit.Data.Roslyn/readme.md b/DragonFruit.Data.Roslyn/readme.md
index 1b8ed44..285cd03 100644
--- a/DragonFruit.Data.Roslyn/readme.md
+++ b/DragonFruit.Data.Roslyn/readme.md
@@ -8,6 +8,8 @@ A Roslyn source-generator and code-analyzer for DragonFruit.Data
DragonFruit.Data.Roslyn is a source-generator and code-analyzer for DragonFruit.Data that allows the request-building logic for `ApiRequest` classes to be generated at compile-time, rather than at runtime for each request.
It also provides code-analysis to ensure attributes are applied correctly and design rules are followed.
+> Some aspects of the source generator are based on [ChilliCream's StrawberryShake code generation tooling](https://github.com/ChilliCream/graphql-platform/tree/13.9.11/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp) (MIT Licensed)
+
## Usage/Getting Started
**Note: while semantic versioning is used, it is best to ensure the versions of `DragonFruit.Data` and `DragonFruit.Data.Roslyn` are the same.**
diff --git a/DragonFruit.Data.Serializers.Html/DragonFruit.Data.Serializers.Html.csproj b/DragonFruit.Data.Serializers.Html/DragonFruit.Data.Serializers.Html.csproj
index f09d924..f27841e 100644
--- a/DragonFruit.Data.Serializers.Html/DragonFruit.Data.Serializers.Html.csproj
+++ b/DragonFruit.Data.Serializers.Html/DragonFruit.Data.Serializers.Html.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/DragonFruit.Data.Tests/DragonFruit.Data.Tests.csproj b/DragonFruit.Data.Tests/DragonFruit.Data.Tests.csproj
index 2546aad..5be099c 100644
--- a/DragonFruit.Data.Tests/DragonFruit.Data.Tests.csproj
+++ b/DragonFruit.Data.Tests/DragonFruit.Data.Tests.csproj
@@ -4,12 +4,13 @@
false
true
net8.0
+ true
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -20,4 +21,8 @@
+
+
+
+
diff --git a/DragonFruit.Data.sln.DotSettings b/DragonFruit.Data.sln.DotSettings
index 7d5ff9f..4478f0a 100644
--- a/DragonFruit.Data.sln.DotSettings
+++ b/DragonFruit.Data.sln.DotSettings
@@ -1,5 +1,8 @@
+ True
+ True
+ ExplicitlyExcluded
False
WRAP_IF_LONG
DragonFruit.Data Copyright DragonFruit Network
@@ -8,8 +11,8 @@ Licensed under the MIT License. Please refer to the LICENSE file at the root of
True
True
True
- ExplicitlyExcluded
- ExplicitlyExcluded
+
+
SOLUTION
WARNING
WARNING
diff --git a/DragonFruit.Data/Converters/ReflectionRequestMessageBuilder.cs b/DragonFruit.Data/Converters/ReflectionRequestMessageBuilder.cs
index 6e94f8c..6776ca4 100644
--- a/DragonFruit.Data/Converters/ReflectionRequestMessageBuilder.cs
+++ b/DragonFruit.Data/Converters/ReflectionRequestMessageBuilder.cs
@@ -23,7 +23,7 @@ public static HttpRequestMessage CreateHttpRequestMessage(ApiRequest request, Se
var requestProperties = requestType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
var requestParams = requestProperties.Select(GetPropertyInfo).Where(x => x != null).ToLookup(x => x.Value.ParameterType, x => (PropertyName: x.Value.ParameterName, x.Value.Accessor));
- var requestUri = new UriBuilder(request.RequestPath);
+ var requestUri = new StringBuilder(request.RequestPath);
// build query
if (requestParams[ParameterType.Query].Any())
@@ -39,11 +39,11 @@ public static HttpRequestMessage CreateHttpRequestMessage(ApiRequest request, Se
{
// trim trailing &
queryBuilder.Length--;
- requestUri.Query = queryBuilder.ToString();
+ requestUri.Append('?').Append(queryBuilder);
}
}
- var requestMessage = new HttpRequestMessage(request.RequestMethod, requestUri.Uri);
+ var requestMessage = new HttpRequestMessage(request.RequestMethod, requestUri.ToString());
// add headers
foreach (var headerParameter in requestParams[ParameterType.Header])