Skip to content

refactor: rework decoders logic for cleaner code#431

Merged
steveiliop56 merged 2 commits intomainfrom
refactor/decoders
Oct 26, 2025
Merged

refactor: rework decoders logic for cleaner code#431
steveiliop56 merged 2 commits intomainfrom
refactor/decoders

Conversation

@steveiliop56
Copy link
Copy Markdown
Member

@steveiliop56 steveiliop56 commented Oct 25, 2025

Summary by CodeRabbit

  • Refactor

    • Internal configuration decoding was reworked to use generic, type-driven logic. No user-facing behavior or public API changes.
  • Tests

    • Updated decoder tests and removed an older normalization test suite.
  • Chores

    • Added an indirect dependency for string case utilities.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 25, 2025

Walkthrough

The diff genericizes decoders and key normalization: NormalizeKeys and DecodeEnv/DecodeFlags are rewritten as generics driven by a reflected type for field names/tags. OAuthServiceConfig field struct tags were changed (some key:"..."field:"...", others had tags removed). Decoder tests and call sites were updated to use explicit type parameters; one test file was removed.

Changes

Cohort / File(s) Summary
Config struct tag updates
internal/config/config.go
Updated struct tags on OAuthServiceConfig: ClientID, RedirectURL, AuthURL, TokenURL, UserinfoURL use field:"..." instead of key:"..."; ClientSecret, ClientSecretFile, Scopes, InsecureSkipVerify, and Name had their tags removed.
Generic decoders & key normalization
internal/utils/decoders/decoders.go
Replaced concrete NormalizeKeys with generic normalizeKeys[T any] and added getKnownKeys[T any]() (reflection-driven key discovery and kebab/case handling). Removed hard-coded config dependency and moved to type-driven key derivation.
Generic env decoder
internal/utils/decoders/env_decoder.go
DecodeEnv becomes DecodeEnv[T any, C any](env map[string]string, subName string) (T, error) and calls normalizeKeys[C](...) before decoding into T.
Generic flags decoder
internal/utils/decoders/flags_decoder.go
DecodeFlags becomes DecodeFlags[T any, C any](flags map[string]string, subName string) (T, error) and calls normalizeKeys[C](...) before decoding into T; removed config import.
Decoder caller updates
internal/utils/app_utils.go
Calls updated to DecodeEnv[config.Providers, config.OAuthServiceConfig](env, "providers") and DecodeFlags[config.Providers, config.OAuthServiceConfig](flags, "providers"), supplying explicit type params and subName.
Decoder tests updated
internal/utils/decoders/env_decoder_test.go, internal/utils/decoders/flags_decoder_test.go
Tests updated to call generic decoders with type params and subName, and simplified expected provider entries (e.g., google, myGithub).
Removed tests
internal/utils/decoders/decoders_test.go
Deleted: original unit tests for NormalizeKeys (environment- and CLI-style key normalization scenarios).
Dependency added
go.mod
Added indirect dependency github.com/stoewer/go-strcase v1.3.1 for case conversion utilities.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Caller
    participant App as app_utils.go
    participant Decoder as Decoder (DecodeEnv / DecodeFlags)
    participant Normalizer as normalizeKeys[C]
    participant Reflect as Reflection (getKnownKeys)
    participant Parser as parser.Decode

    Caller->>App: request config load
    App->>Decoder: DecodeEnv[T,C](env, "providers")
    Decoder->>Normalizer: normalizeKeys[C](env, "providers", "_")
    Normalizer->>Reflect: getKnownKeys[C]()
    Reflect-->>Normalizer: field names + `field` tags
    Normalizer->>Normalizer: build normalized keys (tinyauth.{subName}.field)
    Normalizer-->>Decoder: normalized map
    Decoder->>Parser: parser.Decode("tinyauth.{subName}", &result)
    Parser-->>Decoder: decoded result (type T)
    Decoder-->>App: (T, error)
    App-->>Caller: configured providers (T)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • getKnownKeys[T any]() reflection edge cases (unexported fields, missing/empty tags, tag precedence).
    • Correctness of normalized key suffix matching and separators for both env and flag styles.
    • All callers of DecodeEnv / DecodeFlags to ensure type parameters and subName usage are consistent.
    • Deleted decoders_test.go: verify new tests provide equivalent coverage or add replacements.
    • Differences in constructed decode paths between env and flags decoders.

Possibly related PRs

  • feat: header based acls #337 — Modifies decoders and config types in similar areas (decoders generics and config restructuring); likely overlapping code paths.
  • refactor: unify labels #329 — Alters OAuth-related config in the same file (internal/config/config.go); tag/field changes may interact.

Poem

🐰 With tiny paws I hop through code so neat,
I swap tags, teach decoders new tricks to meet.
Generics hum, reflection plucks each name,
Keys find their path, providers stake their claim.
Hoppity-hop — tests updated, and on we leap! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "refactor: rework decoders logic for cleaner code" directly corresponds to the main changes in the PR. The primary modifications are in the internal/utils/decoders/ package, where the decoder functions (DecodeEnv, DecodeFlags, and NormalizeKeys) have been refactored into generic implementations with reflection-driven key derivation. Supporting changes include updates to how the decoders are invoked in app_utils.go and modifications to struct tags in config.go. The title is concise, specific to the decoders logic that comprises the bulk of the changeset, and avoids generic noise or vague phrasing.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/decoders

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed3b1c4 and 725150f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • go.mod (1 hunks)
  • internal/utils/decoders/decoders.go (2 hunks)
🔇 Additional comments (1)
go.mod (1)

50-50: Dependency addition approved — version is current and secure.

The addition of github.com/stoewer/go-strcase v1.3.1 aligns with the decoder refactoring and leverages generic decoders for case transformations. v1.3.1 is the latest release, and no security advisories exist for this repository. The version is correctly pinned and positioned in the indirect dependencies block.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 25, 2025

Codecov Report

❌ Patch coverage is 91.30435% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 23.15%. Comparing base (c5bb389) to head (725150f).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
internal/utils/decoders/decoders.go 93.75% 1 Missing and 1 partial ⚠️
internal/utils/decoders/env_decoder.go 83.33% 1 Missing ⚠️
internal/utils/decoders/flags_decoder.go 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #431      +/-   ##
==========================================
- Coverage   23.54%   23.15%   -0.40%     
==========================================
  Files          36       36              
  Lines        2862     2237     -625     
==========================================
- Hits          674      518     -156     
+ Misses       2153     1684     -469     
  Partials       35       35              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread internal/utils/decoders/flags_decoder_test.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/utils/decoders/decoders.go (1)

12-61: Env key normalization is incorrect; id extraction trims the wrong prefix.

For env vars like TINYAUTH_PROVIDERS_GOOGLE_CLIENT_ID you lower-case and hyphenate to tinyauth-providers-google-client-id, but then do TrimPrefix(key, root+"-"), which fails (prefix is tinyauth-providers-...). Resulting ids contain tinyauth-providers-..., producing wrong paths like tinyauth.providers.tinyauthProvidersGoogle.clientId.

Also, suffix selection is ambiguous (client-secret matches client-secret-file). Prefer longest-match and require dash-delimited suffix.

Apply:

@@
-func normalizeKeys[T any](input map[string]string, root string, sep string) map[string]string {
-	knownKeys := getKnownKeys[T]()
-	normalized := make(map[string]string)
-
-	for k, v := range input {
-		parts := []string{"tinyauth"}
-
-		key := strings.ToLower(k)
-		key = strings.ReplaceAll(key, sep, "-")
-
-		suffix := ""
-
-		for _, known := range knownKeys {
-			if strings.HasSuffix(key, known) {
-				suffix = known
-				break
-			}
-		}
-
-		if suffix == "" {
-			continue
-		}
-
-		parts = append(parts, root)
-
-		id := strings.TrimPrefix(key, root+"-")
-		id = strings.TrimSuffix(id, "-"+suffix)
-
-		if id == "" {
-			continue
-		}
-
-		parts = append(parts, id)
-		parts = append(parts, suffix)
-
-		final := ""
-
-		for i, part := range parts {
-			if i == 0 {
-				final += kebabToCamel(part)
-				continue
-			}
-			final += "." + kebabToCamel(part)
-		}
-
-		normalized[final] = v
-	}
-
-	return normalized
-}
+func normalizeKeys[T any](input map[string]string, root string, sep string) map[string]string {
+	knownKeys := getKnownKeys[T]() // longest-first
+	normalized := make(map[string]string)
+
+	for k, v := range input {
+		key := strings.ToLower(k)
+		key = strings.ReplaceAll(key, sep, "-")
+
+		var idSuffix string
+		switch {
+		case strings.HasPrefix(key, "tinyauth-"+root+"-"):
+			idSuffix = strings.TrimPrefix(key, "tinyauth-"+root+"-")
+		case strings.HasPrefix(key, root+"-"):
+			idSuffix = strings.TrimPrefix(key, root+"-")
+		default:
+			continue
+		}
+
+		suffix := ""
+		for _, known := range knownKeys { // already length-sorted
+			if strings.HasSuffix(idSuffix, "-"+known) {
+				suffix = known
+				break
+			}
+		}
+		if suffix == "" {
+			continue
+		}
+
+		id := strings.TrimSuffix(idSuffix, "-"+suffix)
+		if id == "" {
+			continue
+		}
+
+		var b strings.Builder
+		b.WriteString("tinyauth.")
+		b.WriteString(kebabToCamel(root))
+		b.WriteByte('.')
+		b.WriteString(kebabToCamel(id))
+		b.WriteByte('.')
+		b.WriteString(kebabToCamel(suffix))
+		normalized[b.String()] = v
+	}
+	return normalized
+}
🧹 Nitpick comments (6)
internal/utils/decoders/decoders.go (1)

94-107: kebabToCamel OK; minor efficiency/style tweaks possible.

Optional: use a strings.Builder and drop the heavy x/text cases by capitalizing via unicode.ToUpper on first rune per segment. Current approach is fine if binary size isn't a concern.

internal/utils/decoders/flags_decoder_test.go (1)

12-18: Test covers flags path; add env variant to prevent regressions.

This validates flag normalization and camelized provider ids (myGithub). Please also add an env test for keys like TINYAUTH_PROVIDERS_MY_GITHUB_CLIENT_ID to catch the prefix bug class.

Example:

@@
 func TestDecodeEnv_Providers(t *testing.T) {
-    // TODO
+    env := map[string]string{
+        "TINYAUTH_PROVIDERS_GOOGLE_CLIENT_ID":        "id",
+        "TINYAUTH_PROVIDERS_GOOGLE_CLIENT_SECRET":    "secret",
+        "TINYAUTH_PROVIDERS_MY_GITHUB_CLIENT_ID":     "gid",
+        "TINYAUTH_PROVIDERS_MY_GITHUB_CLIENT_SECRET": "gsecret",
+    }
+    got, err := decoders.DecodeEnv[config.Providers, config.OAuthServiceConfig](env, "providers")
+    assert.NilError(t, err)
+    assert.Equal(t, got.Providers["google"].ClientID, "id")
+    assert.Equal(t, got.Providers["myGithub"].ClientSecret, "gsecret")
 }

Also applies to: 22-29, 33-37

internal/utils/decoders/flags_decoder.go (3)

9-11: Clarify generic contract: T should be a non-pointer struct

Passing T as a pointer makes &result a **T, which parser.Decode likely won’t handle.

  • Add a doc comment: “T must be a non-pointer struct; C is the element struct for value normalization.”
  • Optionally add a runtime guard (reflect) to fail fast if T is not a struct.

Ensure all call sites use concrete struct types for T (not pointers).


15-16: Simplify Decode prefixes (optional)

Passing both "tinyauth" and "tinyauth."+subName is redundant if normalized keys are rooted at "tinyauth."+subName. Prefer a single, consistent prefix to reduce ambiguity.

-err := parser.Decode(normalized, &result, "tinyauth", "tinyauth."+subName)
+err := parser.Decode(normalized, &result, "tinyauth")

24-29: Preallocate map capacity in filterFlags

Minor perf/alloc improvement.

-func filterFlags(flags map[string]string) map[string]string {
-	filtered := make(map[string]string)
+func filterFlags(flags map[string]string) map[string]string {
+	filtered := make(map[string]string, len(flags))
 	for k, v := range flags {
 		filtered[strings.TrimPrefix(k, "--")] = v
 	}
 	return filtered
 }
internal/utils/decoders/env_decoder_test.go (1)

33-36: Add test cases for hyphenated field tags and slice decoding

The current TestDecodeEnv only covers simple string fields (ClientID, ClientSecret). Extend the test to verify:

  • Hyphenated field tags work end-to-end: PROVIDERS_GOOGLE_REDIRECT_URL, PROVIDERS_GOOGLE_USER_INFO_URL
  • Slice fields with delimiters: PROVIDERS_GOOGLE_SCOPES="openid,email"Scopes: []string{"openid", "email"}

Then add a companion TestDecodeFlags with the same field coverage to confirm delimiter behavior is consistent across both decoder types.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5bb389 and ed3b1c4.

📒 Files selected for processing (8)
  • internal/config/config.go (1 hunks)
  • internal/utils/app_utils.go (2 hunks)
  • internal/utils/decoders/decoders.go (2 hunks)
  • internal/utils/decoders/decoders_test.go (0 hunks)
  • internal/utils/decoders/env_decoder.go (1 hunks)
  • internal/utils/decoders/env_decoder_test.go (1 hunks)
  • internal/utils/decoders/flags_decoder.go (1 hunks)
  • internal/utils/decoders/flags_decoder_test.go (1 hunks)
💤 Files with no reviewable changes (1)
  • internal/utils/decoders/decoders_test.go
🧰 Additional context used
🧬 Code graph analysis (3)
internal/utils/decoders/env_decoder_test.go (2)
internal/config/config.go (2)
  • Providers (176-178)
  • OAuthServiceConfig (55-66)
internal/utils/decoders/env_decoder.go (1)
  • DecodeEnv (7-19)
internal/utils/app_utils.go (3)
internal/utils/decoders/env_decoder.go (1)
  • DecodeEnv (7-19)
internal/config/config.go (2)
  • Providers (176-178)
  • OAuthServiceConfig (55-66)
internal/utils/decoders/flags_decoder.go (1)
  • DecodeFlags (9-22)
internal/utils/decoders/flags_decoder_test.go (2)
internal/config/config.go (2)
  • Providers (176-178)
  • OAuthServiceConfig (55-66)
internal/utils/decoders/flags_decoder.go (1)
  • DecodeFlags (9-22)
🔇 Additional comments (4)
internal/utils/app_utils.go (1)

150-150: Generic decoder calls look good; ensure env path works after normalize fix.

Calls now align with the generic API. Once normalizeKeys is fixed to handle the tinyauth- prefix, GetOAuthProvidersConfig will correctly merge env and flags.

Add/keep an integration test that feeds TINYAUTH_PROVIDERS_GOOGLE_CLIENT_ID/SECRET and asserts providers["google"] is populated via env.

Also applies to: 170-170

internal/utils/decoders/env_decoder.go (1)

7-19: LGTM; relies on normalizeKeys semantics.

Generic signature and parse path are correct. Once normalizeKeys is fixed, env decoding should work for both env and flags consistently.

internal/config/config.go (1)

56-66: The tag migration concern is unsubstantiated—verification shows the implementation is correct.

The decoder implementation in internal/utils/decoders/decoders.go explicitly reads the "field" struct tag via Tag.Get("field") and properly handles fields without tags using fallback kebab-case generation. No legacy key: or auth: tags exist in the codebase. The migration is complete and working correctly.

Likely an incorrect or invalid review comment.

internal/utils/decoders/flags_decoder.go (1)

13-16: Incorrect review comment. Flags use hyphens, not dots.

The test evidence shows flags are hyphen-delimited (e.g., --providers-google-client-id), not dot-delimited as claimed. The normalizeKeys function normalizes its input separator to hyphens; since actual flags already use hyphens, the sep parameter is a no-op regardless of whether it's "_" or ".". The code works correctly as-is, as proven by the passing test.

Likely an incorrect or invalid review comment.

Comment thread internal/utils/decoders/decoders.go
Comment thread internal/utils/decoders/decoders.go Outdated
@steveiliop56 steveiliop56 merged commit 0227af6 into main Oct 26, 2025
7 of 8 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Oct 29, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 22, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 30, 2025
@Rycochet Rycochet deleted the refactor/decoders branch April 1, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants