Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Proteus

## Project Overview

Proteus is a Go library for managing application configuration. It allows developers to define the configuration of an application in a struct and load it from different sources like environment variables and command-line flags. The library also supports configuration updates.

The project uses `golangci-lint` for linting and has a comprehensive test suite.

## Building and Running

### Dependencies

The project has one dependency: `golang.org/x/exp`.

### Linting

To run the linter, use the following command:

```bash
make check
```

This will run `golangci-lint` on the entire codebase.

### Testing

To run the tests, use the following command:

```bash
make test
```

This will run all the tests with the race detector enabled.

### Test Coverage

To generate a test coverage report, use the following command:

```bash
make cover
```

This will generate a coverage report and open it in your browser.

## Development Conventions

The project follows standard Go conventions. All code is formatted using `gofmt`. The project uses a `Makefile` to automate common tasks like linting, testing, and generating coverage reports.
13 changes: 10 additions & 3 deletions parsed.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,19 +330,26 @@ func (p *Parsed) validValue(setName, paramName string, value *string) error {
return fmt.Errorf("param %s.%s does not exit", setName, paramName)
}

if value == nil {
checkRequired := func() error {
if !param.optional {
return types.ErrViolations([]types.Violation{{
SetName: setName,
ParamName: paramName,
Message: "parameter is required but was not specified"}})
Message: "parameter is required but was not specified",
}})
}

return nil
}

if value == nil {
return checkRequired()
}

err := param.validFn(*value)
if err != nil {
if errors.Is(err, types.ErrNoValue) {
return checkRequired()
}
return types.ErrViolations([]types.Violation{{
SetName: setName,
ParamName: paramName,
Expand Down
125 changes: 125 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
package proteus_test

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"math"
"net/url"
"strings"
Expand Down Expand Up @@ -317,3 +321,124 @@ func (t testWriter) Write(v []byte) (int, error) {
t.t.Logf("%s", v)
return len(v), nil
}

func TestRSAPrivateKey(t *testing.T) {
_, privateKeyStr := generateTestKey(t)
defaultKey, _ := generateTestKey(t)

tests := []struct {
name string
params types.ParamValues
shouldErr bool
optionalIsNil bool
useDefault bool
}{
{
name: "valid key for optional and required",
params: types.ParamValues{
"": {
"optionalkey": privateKeyStr,
"requiredkey": privateKeyStr,
},
},
shouldErr: false,
optionalIsNil: false,
},
{
name: "empty string for optional key",
params: types.ParamValues{
"": {
"optionalkey": "",
"requiredkey": privateKeyStr,
},
},
shouldErr: false,
optionalIsNil: true,
},
{
name: "empty string for optional key with default",
params: types.ParamValues{
"": {
"optionalkey": "",
"requiredkey": privateKeyStr,
},
},
shouldErr: false,
optionalIsNil: false,
useDefault: true,
},
{
name: "no value for optional key",
params: types.ParamValues{
"": {
"requiredkey": privateKeyStr,
},
},
shouldErr: false,
optionalIsNil: true,
},
{
name: "empty string for required key",
params: types.ParamValues{
"": {
"requiredkey": "",
},
},
shouldErr: true,
},
{
name: "no value for required key",
params: types.ParamValues{"": {}},
shouldErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := struct {
OptionalKey *xtypes.RSAPrivateKey `param:",optional"`
RequiredKey *xtypes.RSAPrivateKey
}{}

if tt.useDefault {
cfg.OptionalKey = &xtypes.RSAPrivateKey{DefaultValue: defaultKey}
}

testProvider := cfgtest.New(tt.params)
defer testProvider.Stop()

_, err := proteus.MustParse(&cfg,
proteus.WithProviders(testProvider))

if tt.shouldErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
if tt.useDefault {
assert.Equal(t, defaultKey, cfg.OptionalKey.Value())
} else if tt.optionalIsNil {
assert.Equal(t, nil, cfg.OptionalKey.Value())
} else {
assert.NotNil(t, cfg.OptionalKey.Value())
}

if _, ok := tt.params[""]["requiredkey"]; ok && tt.params[""]["requiredkey"] != "" {
assert.NotNil(t, cfg.RequiredKey.Value())
}
}
})
}
}

func generateTestKey(t *testing.T) (*rsa.PrivateKey, string) {
t.Helper()
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("failed to generate RSA private key: %v", err)
}
privateKeyPEM := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
return privateKey, string(pem.EncodeToMemory(privateKeyPEM))
}
1 change: 1 addition & 0 deletions types/dynamic.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:revive
package types

// XType is a configuration parameter parameter that supports being updated
Expand Down
1 change: 1 addition & 0 deletions types/param_value.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:revive
package types

// ParamValues holds the values of the configuration parameters, as read by
Expand Down
9 changes: 9 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
// Package types defines types used by proteus and by code using it.
//
//nolint:revive
package types

import "errors"

// ErrNoValue can be returned by xtypes on their ValueValid method to indicate
// that the provided value should be considered as if no value was provided at
// all. This allows, for example, to handle empty strings as "no value"
var ErrNoValue = errors.New("no value provided")
1 change: 1 addition & 0 deletions types/violation.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:revive
package types

import (
Expand Down
5 changes: 4 additions & 1 deletion xtypes/rsa_priv.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var _ types.Redactor = &RSAPrivateKey{}
// UnmarshalParam parses the input as a string.
func (d *RSAPrivateKey) UnmarshalParam(in *string) error {
var privK *rsa.PrivateKey
if in != nil {
if in != nil && *in != "" {
var err error
privK, err = parseRSAPriv(*in, d.Base64Encoder)
if err != nil {
Expand Down Expand Up @@ -67,6 +67,9 @@ func (d *RSAPrivateKey) Value() *rsa.PrivateKey {
// ValueValid test if the provided parameter value is valid. Has no side
// effects.
func (d *RSAPrivateKey) ValueValid(s string) error {
if s == "" {
return types.ErrNoValue
}
_, err := parseRSAPriv(s, d.Base64Encoder)
return err
}
Expand Down