2024-11-24 12:48:12 +01:00
|
|
|
|
using System.Text;
|
2025-08-30 19:41:10 +02:00
|
|
|
|
using FluentResults;
|
2024-11-24 12:48:12 +01:00
|
|
|
|
using static System.Net.Mime.MediaTypeNames;
|
|
|
|
|
|
|
|
|
|
|
|
namespace pgLabII.PgUtils.ConnectionStrings;
|
|
|
|
|
|
|
|
|
|
|
|
public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly string input;
|
|
|
|
|
|
private int position = 0;
|
|
|
|
|
|
|
2025-08-30 19:41:10 +02:00
|
|
|
|
public bool IsEof
|
2024-11-24 12:48:12 +01:00
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
ConsumeWhitespace();
|
|
|
|
|
|
return position >= input.Length;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public PqConnectionStringTokenizer(string input)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.input = input;
|
|
|
|
|
|
position = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 19:41:10 +02:00
|
|
|
|
public Result<string> GetKeyword()
|
2024-11-24 12:48:12 +01:00
|
|
|
|
{
|
2025-08-30 19:41:10 +02:00
|
|
|
|
if (IsEof)
|
|
|
|
|
|
return Result.Fail($"Unexpected end of file was expecting a keyword at position {position}");
|
2024-11-24 12:48:12 +01:00
|
|
|
|
|
|
|
|
|
|
return GetString(forKeyword: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 19:41:10 +02:00
|
|
|
|
public Result ConsumeEquals()
|
2024-11-24 12:48:12 +01:00
|
|
|
|
{
|
|
|
|
|
|
ConsumeWhitespace();
|
|
|
|
|
|
if (position < input.Length && input[position] == '=')
|
|
|
|
|
|
{
|
|
|
|
|
|
position++;
|
2025-08-30 19:41:10 +02:00
|
|
|
|
return Result.Ok();
|
2024-11-24 12:48:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2025-08-30 19:41:10 +02:00
|
|
|
|
return Result.Fail($"Was expecting '=' after keyword at position {position}");
|
2024-11-24 12:48:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 19:41:10 +02:00
|
|
|
|
public Result<string> GetValue()
|
2024-11-24 12:48:12 +01:00
|
|
|
|
{
|
2025-08-30 19:41:10 +02:00
|
|
|
|
if (IsEof)
|
|
|
|
|
|
return Result.Fail($"Unexpected end of file was expecting a keyword at position {position}");
|
2024-11-24 12:48:12 +01:00
|
|
|
|
|
|
|
|
|
|
return GetString(forKeyword: false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 19:41:10 +02:00
|
|
|
|
private Result<string> GetString(bool forKeyword)
|
2024-11-24 12:48:12 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (forKeyword && input[position] == '=')
|
2025-08-30 19:41:10 +02:00
|
|
|
|
return Result.Fail($"Unexpected '=' was expecting keyword at position {position}");
|
2024-11-24 12:48:12 +01:00
|
|
|
|
|
|
|
|
|
|
if (input[position] == '\'')
|
|
|
|
|
|
return ParseQuotedText();
|
|
|
|
|
|
|
|
|
|
|
|
return UnquotedString(forKeyword);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ConsumeWhitespace()
|
|
|
|
|
|
{
|
|
|
|
|
|
while (position < input.Length && char.IsWhiteSpace(input[position]))
|
|
|
|
|
|
position++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string UnquotedString(bool forKeyword)
|
|
|
|
|
|
{
|
|
|
|
|
|
int start = position;
|
2025-08-31 10:12:22 +02:00
|
|
|
|
while (++position < input.Length)
|
|
|
|
|
|
{
|
|
|
|
|
|
char c = input[position];
|
|
|
|
|
|
// Libpq syntax does not use semicolons as pair separators; treat ';' as invalid here
|
|
|
|
|
|
if (c == ';')
|
|
|
|
|
|
{
|
|
|
|
|
|
// Force tokenizer to stop and later cause a parse error by making GetValue/keyword incomplete
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (char.IsWhiteSpace(c)) break;
|
|
|
|
|
|
if (forKeyword && c == '=') break;
|
|
|
|
|
|
}
|
2024-11-24 12:48:12 +01:00
|
|
|
|
return input.Substring(start, position - start);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 19:41:10 +02:00
|
|
|
|
private Result<string> ParseQuotedText()
|
2024-11-24 12:48:12 +01:00
|
|
|
|
{
|
|
|
|
|
|
bool escape = false;
|
|
|
|
|
|
StringBuilder sb = new();
|
|
|
|
|
|
int start = position;
|
|
|
|
|
|
while (++position < input.Length)
|
|
|
|
|
|
{
|
|
|
|
|
|
char c = input[position];
|
|
|
|
|
|
if (escape)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (c)
|
|
|
|
|
|
{
|
|
|
|
|
|
case '\'':
|
|
|
|
|
|
case '\\':
|
|
|
|
|
|
sb.Append(c);
|
|
|
|
|
|
escape = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
2025-08-30 19:41:10 +02:00
|
|
|
|
return Result.Fail($"Invalid escape sequence at position {position}");
|
2024-11-24 12:48:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (c == '\'')
|
|
|
|
|
|
{
|
|
|
|
|
|
++position;
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (c == '\\')
|
|
|
|
|
|
{
|
|
|
|
|
|
escape = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
sb.Append(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-30 19:41:10 +02:00
|
|
|
|
return Result.Fail($"Missing end quote on value starting at {start}");
|
2024-11-24 12:48:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|