Merge pull request 'database: initial import for dsn parsing' (#275) from database into master
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				/ autoupdate (push) Failing after 1m28s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	/ autoupdate (push) Failing after 1m28s
				
			Reviewed-on: #275
This commit was merged in pull request #275.
	This commit is contained in:
		
							
								
								
									
										114
									
								
								database/dsn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								database/dsn.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| package database | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrInvalidDSNAddr      = errors.New("invalid dsn addr") | ||||
| 	ErrInvalidDSNUnescaped = errors.New("dsn must be escaped") | ||||
| 	ErrInvalidDSNNoSlash   = errors.New("dsn must contains slash") | ||||
| ) | ||||
|  | ||||
| type Config struct { | ||||
| 	Params    map[string]string | ||||
| 	TLSConfig *tls.Config | ||||
| 	Username  string | ||||
| 	Password  string | ||||
| 	Scheme    string | ||||
| 	Host      string | ||||
| 	Port      string | ||||
| 	Database  string | ||||
| } | ||||
|  | ||||
| func ParseDSN(dsn string) (*Config, error) { | ||||
| 	cfg := &Config{} | ||||
|  | ||||
| 	// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN] | ||||
| 	// Find last '/' that goes before dbname | ||||
| 	foundSlash := false | ||||
| 	for i := len(dsn) - 1; i >= 0; i-- { | ||||
| 		if dsn[i] == '/' { | ||||
| 			foundSlash = true | ||||
| 			var j, k int | ||||
|  | ||||
| 			// left part is empty if i <= 0 | ||||
| 			if i > 0 { | ||||
| 				// Find the first ':' in dsn | ||||
| 				for j = i; j >= 0; j-- { | ||||
| 					if dsn[j] == ':' { | ||||
| 						cfg.Scheme = dsn[0:j] | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// [username[:password]@][host] | ||||
| 				// Find the last '@' in dsn[:i] | ||||
| 				for j = i; j >= 0; j-- { | ||||
| 					if dsn[j] == '@' { | ||||
| 						// username[:password] | ||||
| 						// Find the second ':' in dsn[:j] | ||||
| 						for k = 0; k < j; k++ { | ||||
| 							if dsn[k] == ':' { | ||||
| 								if cfg.Scheme == dsn[:k] { | ||||
| 									continue | ||||
| 								} | ||||
| 								var err error | ||||
| 								cfg.Password, err = url.PathUnescape(dsn[k+1 : j]) | ||||
| 								if err != nil { | ||||
| 									return nil, err | ||||
| 								} | ||||
| 								break | ||||
| 							} | ||||
| 						} | ||||
| 						cfg.Username = dsn[len(cfg.Scheme)+3 : k] | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				for k = j + 1; k < i; k++ { | ||||
| 					if dsn[k] == ':' { | ||||
| 						cfg.Host = dsn[j+1 : k] | ||||
| 						cfg.Port = dsn[k+1 : i] | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 			} | ||||
|  | ||||
| 			// dbname[?param1=value1&...¶mN=valueN] | ||||
| 			// Find the first '?' in dsn[i+1:] | ||||
| 			for j = i + 1; j < len(dsn); j++ { | ||||
| 				if dsn[j] == '?' { | ||||
| 					parts := strings.Split(dsn[j+1:], "&") | ||||
| 					cfg.Params = make(map[string]string, len(parts)) | ||||
| 					for _, p := range parts { | ||||
| 						k, v, found := strings.Cut(p, "=") | ||||
| 						if !found { | ||||
| 							continue | ||||
| 						} | ||||
| 						cfg.Params[k] = v | ||||
| 					} | ||||
|  | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			var err error | ||||
| 			dbname := dsn[i+1 : j] | ||||
| 			if cfg.Database, err = url.PathUnescape(dbname); err != nil { | ||||
| 				return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err) | ||||
| 			} | ||||
|  | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !foundSlash && len(dsn) > 0 { | ||||
| 		return nil, ErrInvalidDSNNoSlash | ||||
| 	} | ||||
|  | ||||
| 	return cfg, nil | ||||
| } | ||||
							
								
								
									
										15
									
								
								database/dsn_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								database/dsn_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package database | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestParseDSN(t *testing.T) { | ||||
| 	cfg, err := ParseDSN("postgres://username:p@ssword#@host:12345/dbname?key1=val2&key2=val2") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if cfg.Password != "p@ssword#" { | ||||
| 		t.Fatalf("parsing error") | ||||
| 	} | ||||
| } | ||||
| @@ -2,7 +2,6 @@ package logger | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| @@ -70,7 +69,6 @@ func WithContextAttrFuncs(fncs ...ContextAttrFunc) options.Option { | ||||
| 		for _, l := range fncs { | ||||
| 			cv = reflect.Append(cv, reflect.ValueOf(l)) | ||||
| 		} | ||||
| 		fmt.Printf("EEEE %#+v\n", cv.Interface()) | ||||
| 		return options.Set(src, cv.Interface(), ".ContextAttrFuncs") | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user