pq connection string parsing from other project
This commit is contained in:
parent
9a5feb9d54
commit
d803cd8003
10 changed files with 529 additions and 0 deletions
|
|
@ -0,0 +1,41 @@
|
|||
using pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
|
||||
|
||||
public class PqConnectionStringParserTests
|
||||
{
|
||||
private readonly UnitTestTokenizer tokenizer = new();
|
||||
|
||||
private const string kw = "ab";
|
||||
private const string val = "cd";
|
||||
|
||||
public PqConnectionStringParserTests()
|
||||
{
|
||||
tokenizer
|
||||
.AddString(kw)
|
||||
.AddEquals()
|
||||
.AddString(val);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Success()
|
||||
{
|
||||
var parser = new PqConnectionStringParser(tokenizer);
|
||||
IDictionary<string, string> output = parser.Parse();
|
||||
|
||||
Assert.Single(output);
|
||||
Assert.True(output.TryGetValue(kw, out string? result));
|
||||
Assert.Equal(val, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticParse()
|
||||
{
|
||||
var output = PqConnectionStringParser.Parse("foo=bar");
|
||||
Assert.Single(output);
|
||||
Assert.True(output.TryGetValue("foo", out string? result));
|
||||
Assert.Equal("bar", result);
|
||||
}
|
||||
// There are few tests here as this is a predictive parser and all error handling is done
|
||||
// in the tokenizer
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
using pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
|
||||
|
||||
public class PqConnectionStringTokenizerTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("abc", "abc")]
|
||||
[InlineData("abc=", "abc")]
|
||||
[InlineData(" abc =", "abc")]
|
||||
public void GetKeyword_Success(string input, string expected)
|
||||
{
|
||||
PqConnectionStringTokenizer subject = new(input);
|
||||
|
||||
Assert.Equal(expected, subject.GetKeyword());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("=")]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void GetKeyword_Throws(string input)
|
||||
{
|
||||
PqConnectionStringTokenizer subject = new(input);
|
||||
Assert.Throws<PqConnectionStringParserException>(() => subject.GetKeyword());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", true)]
|
||||
[InlineData(" ", true)]
|
||||
[InlineData(" \t", true)]
|
||||
[InlineData("d", false)]
|
||||
[InlineData("=", false)]
|
||||
[InlineData(".", false)]
|
||||
public void Eof(string input, bool expected)
|
||||
{
|
||||
PqConnectionStringTokenizer subject = new(input);
|
||||
|
||||
Assert.Equal(expected, subject.Eof);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("=")]
|
||||
[InlineData("=test")]
|
||||
[InlineData(" = ")]
|
||||
public void ConsumeEquals_Success(string input)
|
||||
{
|
||||
PqConnectionStringTokenizer subject = new(input);
|
||||
subject.ConsumeEquals();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("t")]
|
||||
[InlineData(" test")]
|
||||
[InlineData(" ")]
|
||||
public void ConsumeEquals_Throws(string input)
|
||||
{
|
||||
PqConnectionStringTokenizer subject = new(input);
|
||||
Assert.Throws<PqConnectionStringParserException>(() => subject.ConsumeEquals());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("foo", "foo")]
|
||||
[InlineData("foo ", "foo")]
|
||||
[InlineData("foo=", "foo=")]
|
||||
[InlineData("1.2.3.4", "1.2.3.4")]
|
||||
[InlineData(@"'foo \'bar' ", "foo 'bar")]
|
||||
public void GetValue_Success(string input, string expected)
|
||||
{
|
||||
PqConnectionStringTokenizer subject = new(input);
|
||||
Assert.Equal(expected, subject.GetValue());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("'d")]
|
||||
[InlineData(@"'\d'")]
|
||||
public void GetValue_Throws(string input)
|
||||
{
|
||||
PqConnectionStringTokenizer subject = new(input);
|
||||
Assert.Throws<PqConnectionStringParserException>(() => subject.GetValue());
|
||||
}
|
||||
}
|
||||
9
pgLabII.PgUtils.Tests/ConnectionStrings/Util/PqToken.cs
Normal file
9
pgLabII.PgUtils.Tests/ConnectionStrings/Util/PqToken.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
|
||||
|
||||
internal enum PqToken
|
||||
{
|
||||
String,
|
||||
Equals,
|
||||
Eof,
|
||||
Exception
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
using pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
|
||||
|
||||
internal class UnitTestTokenizer : IPqConnectionStringTokenizer
|
||||
{
|
||||
private readonly struct Elem(PqToken result, object? output)
|
||||
{
|
||||
public readonly PqToken Result = result;
|
||||
public readonly object? Output = output;
|
||||
}
|
||||
|
||||
private readonly List<Elem> _tokens = [];
|
||||
private int _position = 0;
|
||||
|
||||
|
||||
public UnitTestTokenizer AddString(string? output)
|
||||
{
|
||||
_tokens.Add(new(PqToken.String, output));
|
||||
return this;
|
||||
}
|
||||
|
||||
public UnitTestTokenizer AddException(Exception? output)
|
||||
{
|
||||
_tokens.Add(new(PqToken.Exception, output));
|
||||
return this;
|
||||
}
|
||||
|
||||
public UnitTestTokenizer AddEquals()
|
||||
{
|
||||
_tokens.Add(new(PqToken.Equals, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
// note we do no whitespace at end tests here
|
||||
public bool Eof => _position >= _tokens.Count;
|
||||
|
||||
public string GetKeyword()
|
||||
{
|
||||
EnsureNotEof();
|
||||
var elem = Consume();
|
||||
if (elem.Result == PqToken.String)
|
||||
return (string)elem.Output!;
|
||||
|
||||
throw new Exception("Unexpected call to GetKeyword");
|
||||
}
|
||||
|
||||
|
||||
public void ConsumeEquals()
|
||||
{
|
||||
EnsureNotEof();
|
||||
var elem = Consume();
|
||||
if (elem.Result == PqToken.Equals)
|
||||
return;
|
||||
|
||||
throw new Exception("Unexpected call to ConsumeEquals");
|
||||
}
|
||||
|
||||
public string GetValue()
|
||||
{
|
||||
EnsureNotEof();
|
||||
var elem = Consume();
|
||||
if (elem.Result == PqToken.String)
|
||||
return (string)elem.Output!;
|
||||
|
||||
throw new Exception("Unexpected call to GetValue");
|
||||
}
|
||||
|
||||
private Elem Consume()
|
||||
{
|
||||
var elem = _tokens[_position++];
|
||||
if (elem.Result == PqToken.Exception)
|
||||
{
|
||||
throw (Exception)elem.Output!;
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
private void EnsureNotEof()
|
||||
{
|
||||
if (Eof)
|
||||
throw new Exception("unexpected eof in test, wrong parser call?");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
namespace pgLabII.PgUtils.Tests.ConnectionStrings.Util;
|
||||
|
||||
public class UnitTestTokenizerTests
|
||||
{
|
||||
private readonly UnitTestTokenizer _sut = new();
|
||||
|
||||
[Fact]
|
||||
public void Eof_True()
|
||||
{
|
||||
Assert.True(_sut.Eof);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Eof_False()
|
||||
{
|
||||
_sut.AddString("a");
|
||||
Assert.False(_sut.Eof);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetKeyword_Success()
|
||||
{
|
||||
_sut.AddString("a");
|
||||
Assert.Equal("a", _sut.GetKeyword());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetKeyword_Unexpected_Throws()
|
||||
{
|
||||
_sut.AddEquals();
|
||||
Assert.Throws<Exception>(() => _sut.GetKeyword());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetKeyword_SimulatesException()
|
||||
{
|
||||
_sut.AddException(new ArgumentNullException());
|
||||
Assert.Throws<ArgumentNullException>(() => _sut.GetKeyword());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetValue_Success()
|
||||
{
|
||||
_sut.AddString("a");
|
||||
Assert.Equal("a", _sut.GetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetValue_Unexpected_Throws()
|
||||
{
|
||||
_sut.AddEquals();
|
||||
Assert.Throws<Exception>(() => _sut.GetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetValue_SimulatesException()
|
||||
{
|
||||
_sut.AddException(new ArgumentNullException());
|
||||
Assert.Throws<ArgumentNullException>(() => _sut.GetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsumeEquals_Success()
|
||||
{
|
||||
_sut.AddEquals();
|
||||
_sut.ConsumeEquals();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsumeEquals_Unexpected_Throws1()
|
||||
{
|
||||
Assert.Throws<Exception>(() => _sut.ConsumeEquals());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsumeEquals_Unexpected_Throws2()
|
||||
{
|
||||
_sut.AddString("t");
|
||||
Assert.Throws<Exception>(() => _sut.ConsumeEquals());
|
||||
}
|
||||
}
|
||||
7
pgLabII.PgUtils/ConnectionStrings/PgConfigMapping.cs
Normal file
7
pgLabII.PgUtils/ConnectionStrings/PgConfigMapping.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public readonly record struct PgConfigMapping(
|
||||
string pqKeyword,
|
||||
string? pqDumpParam,
|
||||
string? pqEnvironment
|
||||
);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public interface IPqConnectionStringTokenizer
|
||||
{
|
||||
bool Eof { get; }
|
||||
//PqToken NextToken(out string? text);
|
||||
string GetKeyword();
|
||||
void ConsumeEquals();
|
||||
string GetValue();
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public class PqConnectionStringParser
|
||||
{
|
||||
// Note possible keywords
|
||||
// host
|
||||
//hostaddr
|
||||
//port
|
||||
//dbname
|
||||
//user
|
||||
//password
|
||||
//passfile
|
||||
//channel_binding
|
||||
//connect_timeout
|
||||
//client_encoding
|
||||
//options
|
||||
//application_name
|
||||
//fallback_application_name
|
||||
//keepalives
|
||||
//keepalives_idle
|
||||
//keepalives_interval
|
||||
//keepalives_count
|
||||
//tcp_user_timeout
|
||||
//replication
|
||||
//gssencmode
|
||||
//sslmode
|
||||
//requiresll
|
||||
//sslcompression
|
||||
//sslcert
|
||||
//sslkey
|
||||
//sslpassword
|
||||
//sslrootcert
|
||||
//sslcrl
|
||||
//sslcrldir
|
||||
//sslsni
|
||||
//requirepeer
|
||||
//ssl_min_protocol_version
|
||||
//ssl_max_protocol_version
|
||||
//krbsrvname
|
||||
//gsslib
|
||||
//service
|
||||
//target_session_attrs
|
||||
|
||||
public static IDictionary<string, string> Parse(string input)
|
||||
{
|
||||
return new PqConnectionStringParser(
|
||||
new PqConnectionStringTokenizer(input)
|
||||
).Parse();
|
||||
}
|
||||
|
||||
private readonly IPqConnectionStringTokenizer tokenizer;
|
||||
private readonly Dictionary<string, string> result = new();
|
||||
|
||||
public PqConnectionStringParser(IPqConnectionStringTokenizer tokenizer)
|
||||
{
|
||||
this.tokenizer = tokenizer;
|
||||
}
|
||||
|
||||
public IDictionary<string, string> Parse()
|
||||
{
|
||||
result.Clear();
|
||||
|
||||
while (!tokenizer.Eof)
|
||||
ParsePair();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ParsePair()
|
||||
{
|
||||
string kw = tokenizer.GetKeyword();
|
||||
tokenizer.ConsumeEquals();
|
||||
string v = tokenizer.GetValue();
|
||||
result.Add(kw, v);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public class PqConnectionStringParserException : Exception
|
||||
{
|
||||
public PqConnectionStringParserException()
|
||||
{
|
||||
}
|
||||
|
||||
public PqConnectionStringParserException(string? message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public PqConnectionStringParserException(string? message, Exception? innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
using System.Text;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace pgLabII.PgUtils.ConnectionStrings;
|
||||
|
||||
public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
|
||||
{
|
||||
private readonly string input;
|
||||
private int position = 0;
|
||||
|
||||
public bool Eof
|
||||
{
|
||||
get
|
||||
{
|
||||
ConsumeWhitespace();
|
||||
return position >= input.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public PqConnectionStringTokenizer(string input)
|
||||
{
|
||||
this.input = input;
|
||||
position = 0;
|
||||
}
|
||||
|
||||
public string GetKeyword()
|
||||
{
|
||||
if (Eof)
|
||||
throw new PqConnectionStringParserException($"Unexpected end of file was expecting a keyword at position {position}");
|
||||
|
||||
return GetString(forKeyword: true);
|
||||
}
|
||||
|
||||
public void ConsumeEquals()
|
||||
{
|
||||
ConsumeWhitespace();
|
||||
if (position < input.Length && input[position] == '=')
|
||||
{
|
||||
position++;
|
||||
}
|
||||
else
|
||||
throw new PqConnectionStringParserException($"Was expecting '=' after keyword at position {position}");
|
||||
}
|
||||
|
||||
public string GetValue()
|
||||
{
|
||||
if (Eof)
|
||||
throw new PqConnectionStringParserException($"Unexpected end of file was expecting a keyword at position {position}");
|
||||
|
||||
return GetString(forKeyword: false);
|
||||
}
|
||||
|
||||
private string GetString(bool forKeyword)
|
||||
{
|
||||
if (forKeyword && input[position] == '=')
|
||||
throw new PqConnectionStringParserException($"Unexpected '=' was expecting keyword at position {position}");
|
||||
|
||||
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;
|
||||
while (++position < input.Length && !char.IsWhiteSpace(input[position]) && (!forKeyword || input[position] != '='))
|
||||
{ }
|
||||
return input.Substring(start, position - start);
|
||||
}
|
||||
|
||||
private string ParseQuotedText()
|
||||
{
|
||||
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:
|
||||
throw new PqConnectionStringParserException($"Invalid escape sequence at position {position}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c == '\'')
|
||||
{
|
||||
++position;
|
||||
return sb.ToString();
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
escape = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new PqConnectionStringParserException($"Missing end quote on value starting at {start}");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue