Skip to content

Conversation

@oschwald
Copy link
Owner

@oschwald oschwald commented Dec 22, 2025

This adds the AnonymousPlus() method for querying the GeoIP Anonymous Plus
database, which provides VPN detection with confidence scoring, provider
identification, and temporal tracking.

New features:

  • AnonymousPlus struct with all standard anonymous IP flags plus:
    • AnonymizerConfidence (uint16): confidence score 1-99
    • ProviderName (string): VPN provider name
    • NetworkLastSeen (Date): last sighting date
  • Date type that parses ISO date strings from MMDB and serializes to JSON
    as ISO date format

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Added support for the GeoIP Anonymous Plus database: VPN detection with confidence scores, provider identification, and network last-seen tracking.
  • Documentation

    • Added Anonymous Plus usage section to the README (note: duplicated content was added).
  • Bug Fixes / Deprecation

    • Clarified that StaticIPScore was added in error and will be removed in the next major release; IsLegitimateProxy deprecation unchanged.
  • Tests / Examples

    • Added example and tests demonstrating Anonymous Plus behavior and data presence checks.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 22, 2025

Walkthrough

Adds support for the GeoIP Anonymous Plus database: new AnonymousPlus type and Date type, a Reader.AnonymousPlus() method, tests and example, README and CHANGELOG updates, and test-data submodule pointer update.

Changes

Cohort / File(s) Summary
Changelog & Docs
CHANGELOG.md, README.md
Add 2.1.0 entry and README section describing the Anonymous Plus database and usage example (README content duplicated). Clarify deprecation note for StaticIPScore on EnterpriseTraits.
Models
models.go
Add Date type with UnmarshalMaxMindDB and MarshalJSON. Add exported AnonymousPlus struct (IPAddress, Network, NetworkLastSeen, ProviderName, AnonymizerConfidence, boolean flags) and HasData() method. Add imports for fmt, time, and mmdbdata.
Reader / DB Detection
reader.go
Add isAnonymousPlus DB type constant, extend getDBType() to recognize "GeoIP-Anonymous-Plus", and implement func (r *Reader) AnonymousPlus(ipAddress netip.Addr) (*AnonymousPlus, error) performing lookup, decode, and field population.
Examples & Tests
example_test.go, reader_test.go
Add ExampleReader_AnonymousPlus() example. Add TestAnonymousPlus() exercising full/minimal/private-data cases and include AnonymousPlus in TestAllStructsHaveHasData. Update imports for tests (add time).
Test Data
test-data/...
Update test-data submodule pointer (tracked revision bump).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review focus:
    • Date MMDB unmarshalling and JSON marshaling edge cases
    • AnonymousPlus field types and HasData() correctness
    • Reader.AnonymousPlus error handling and correct population of IPAddress/Network
    • Tests covering minimal and private-IP behaviors

Poem

🐰 I found a plus of anonymous light,
VPNs and providers came into sight,
Dates and confidences parsed by my paw,
Tests hopped along to inspect every flaw,
Hooray — a new hop in the GeoIP night! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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 PR title 'Add support for GeoIP Anonymous Plus database' accurately and concisely describes the main change: adding a new method and struct to support querying the GeoIP Anonymous Plus database.
✨ 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 greg/anon-plus

📜 Recent review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between add2bf3 and 16f5bfa.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • README.md
  • example_test.go
  • models.go
  • reader.go
  • reader_test.go
  • test-data
🧰 Additional context used
🧬 Code graph analysis (3)
reader_test.go (2)
reader.go (1)
  • Open (126-133)
models.go (2)
  • AnonymousPlus (561-592)
  • Date (13-15)
example_test.go (2)
reader.go (1)
  • Open (126-133)
models.go (1)
  • AnonymousPlus (561-592)
reader.go (1)
models.go (1)
  • AnonymousPlus (561-592)
🪛 markdownlint-cli2 (0.18.1)
README.md

333-333: Hard tabs
Column: 1

(MD010, no-hard-tabs)


334-334: Hard tabs
Column: 1

(MD010, no-hard-tabs)


335-335: Hard tabs
Column: 1

(MD010, no-hard-tabs)


337-337: Hard tabs
Column: 1

(MD010, no-hard-tabs)


341-341: Hard tabs
Column: 1

(MD010, no-hard-tabs)


342-342: Hard tabs
Column: 1

(MD010, no-hard-tabs)


343-343: Hard tabs
Column: 1

(MD010, no-hard-tabs)


344-344: Hard tabs
Column: 1

(MD010, no-hard-tabs)


345-345: Hard tabs
Column: 1

(MD010, no-hard-tabs)


347-347: Hard tabs
Column: 1

(MD010, no-hard-tabs)


348-348: Hard tabs
Column: 1

(MD010, no-hard-tabs)


349-349: Hard tabs
Column: 1

(MD010, no-hard-tabs)


350-350: Hard tabs
Column: 1

(MD010, no-hard-tabs)


352-352: Hard tabs
Column: 1

(MD010, no-hard-tabs)


353-353: Hard tabs
Column: 1

(MD010, no-hard-tabs)


354-354: Hard tabs
Column: 1

(MD010, no-hard-tabs)


355-355: Hard tabs
Column: 1

(MD010, no-hard-tabs)


357-357: Hard tabs
Column: 1

(MD010, no-hard-tabs)


358-358: Hard tabs
Column: 1

(MD010, no-hard-tabs)


359-359: Hard tabs
Column: 1

(MD010, no-hard-tabs)


360-360: Hard tabs
Column: 1

(MD010, no-hard-tabs)


362-362: Hard tabs
Column: 1

(MD010, no-hard-tabs)


363-363: Hard tabs
Column: 1

(MD010, no-hard-tabs)


364-364: Hard tabs
Column: 1

(MD010, no-hard-tabs)


365-365: Hard tabs
Column: 1

(MD010, no-hard-tabs)


366-366: Hard tabs
Column: 1

(MD010, no-hard-tabs)


367-367: Hard tabs
Column: 1

(MD010, no-hard-tabs)


368-368: Hard tabs
Column: 1

(MD010, no-hard-tabs)


370-370: Hard tabs
Column: 1

(MD010, no-hard-tabs)


371-371: Hard tabs
Column: 1

(MD010, no-hard-tabs)


372-372: Hard tabs
Column: 1

(MD010, no-hard-tabs)


373-373: Hard tabs
Column: 1

(MD010, no-hard-tabs)


374-374: Hard tabs
Column: 1

(MD010, no-hard-tabs)


375-375: Hard tabs
Column: 1

(MD010, no-hard-tabs)


377-377: Hard tabs
Column: 1

(MD010, no-hard-tabs)


378-378: Hard tabs
Column: 1

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build (1.25.x, windows-latest)
🔇 Additional comments (12)
test-data (1)

1-1: The submodule pointer update aligns with the new AnonymousPlus feature. The code implementation includes the AnonymousPlus struct with fields matching the GeoIP-Anonymous-Plus database schema (including anonymizer_confidence, provider_name, and network_last_seen), and tests reference the expected GeoIP-Anonymous-Plus-Test.mmdb test file.

reader.go (3)

65-65: LGTM!

The new isAnonymousPlus constant is correctly added to the databaseType iota block, following the established bitflag pattern used for other database types.


150-151: LGTM!

The getDBType switch case for "GeoIP-Anonymous-Plus" is correctly implemented, returning the new isAnonymousPlus type.


262-278: LGTM!

The AnonymousPlus() method follows the established pattern used by other lookup methods (e.g., AnonymousIP(), ASN()):

  • Validates database type support
  • Performs lookup and decodes result
  • Sets IPAddress and Network fields
  • Returns properly on error

The implementation is consistent and correct.

reader_test.go (3)

8-8: LGTM!

The time import is correctly added to support the date assertions in TestAnonymousPlus.


169-221: LGTM!

Comprehensive test coverage for AnonymousPlus:

  • Tests full data scenario with all Anonymous Plus-specific fields (AnonymizerConfidence, ProviderName, NetworkLastSeen)
  • Tests minimal data scenario where optional fields are zero/empty
  • Tests private IP scenario confirming HasData() returns false while IPAddress and Network remain populated
  • Follows the established test patterns from other database type tests

487-499: LGTM!

AnonymousPlus is correctly added to the TestAllStructsHaveHasData test, ensuring that the HasData() method returns false for zero-value structs, consistent with all other result types.

CHANGELOG.md (1)

3-12: LGTM!

The changelog entry for version 2.1.0 is well-written:

  • Accurately describes the new Anonymous Plus database support with its key features (confidence scoring, provider identification, temporal tracking)
  • Appropriately documents the StaticIPScore deprecation with clear explanation
  • Minor version bump is correct for a new feature addition
README.md (1)

324-380: LGTM!

The new Anonymous Plus Database section is comprehensive and well-documented:

  • Clear description of the database's extended capabilities
  • Code example demonstrates proper usage including HasData() check
  • Shows both standard anonymous IP flags and Plus-specific fields (AnonymizerConfidence, ProviderName, NetworkLastSeen)
  • Uses IsZero() check for NetworkLastSeen before formatting, which is a good practice

The hard tab warnings from markdownlint are false positives for Go code blocks, where tabs are conventional.

models.go (3)

3-9: LGTM!

The new imports are correctly added:

  • fmt for error formatting in Date.UnmarshalMaxMindDB
  • time for the Date type implementation
  • mmdbdata for the MMDB decoder interface

11-40: LGTM!

The Date type is well-implemented:

  • Embedding time.Time: Provides access to all time.Time methods (including IsZero(), Format())
  • UnmarshalMaxMindDB: Correctly parses ISO date strings using time.DateOnly, handles empty strings gracefully, and wraps errors with context
  • MarshalJSON: Properly returns "null" for zero dates and ISO-formatted strings otherwise

The implementation follows Go conventions and integrates cleanly with the MMDB decoder.


558-606: LGTM!

The AnonymousPlus struct and HasData() method are correctly implemented:

Struct:

  • Includes all standard Anonymous IP boolean flags
  • Adds Plus-specific fields: NetworkLastSeen (Date), ProviderName (string), AnonymizerConfidence (uint16)
  • JSON and maxminddb tags are consistent and properly aligned
  • Documentation is clear and matches the PR objectives

HasData():

  • Correctly excludes IPAddress and Network from the check (consistent with other types)
  • Checks all data fields including !a.NetworkLastSeen.IsZero() for temporal tracking
  • Logical OR structure ensures any non-zero data returns true

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

This adds the AnonymousPlus() method for querying the GeoIP Anonymous Plus database, which provides VPN detection with confidence scoring, provider identification, and temporal tracking. New features: - AnonymousPlus struct with all standard anonymous IP flags plus: - AnonymizerConfidence (uint16): confidence score 1-99 - ProviderName (string): VPN provider name - NetworkLastSeen (Date): last sighting date - Date type that parses ISO date strings from MMDB and serializes to JSON as ISO date format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@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

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c814ad and add2bf3.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • README.md
  • example_test.go
  • models.go
  • reader.go
  • reader_test.go
🧰 Additional context used
🧬 Code graph analysis (2)
reader.go (1)
models.go (1)
  • AnonymousPlus (561-592)
example_test.go (2)
reader.go (1)
  • Open (126-133)
models.go (1)
  • AnonymousPlus (561-592)
🪛 GitHub Actions: Go
example_test.go

[error] 169-169: ExampleReader_AnonymousPlus failed: open test-data/test-data/GeoIP-Anonymous-Plus-Test.mmdb: no such file or directory. (Command: go test -race -v ./...)

reader_test.go

[error] 171-171: TestAnonymousPlus failed: open test-data/test-data/GeoIP-Anonymous-Plus-Test.mmdb: no such file or directory. (Command: go test -race -v ./...)

🪛 markdownlint-cli2 (0.18.1)
README.md

333-333: Hard tabs
Column: 1

(MD010, no-hard-tabs)


334-334: Hard tabs
Column: 1

(MD010, no-hard-tabs)


335-335: Hard tabs
Column: 1

(MD010, no-hard-tabs)


337-337: Hard tabs
Column: 1

(MD010, no-hard-tabs)


341-341: Hard tabs
Column: 1

(MD010, no-hard-tabs)


342-342: Hard tabs
Column: 1

(MD010, no-hard-tabs)


343-343: Hard tabs
Column: 1

(MD010, no-hard-tabs)


344-344: Hard tabs
Column: 1

(MD010, no-hard-tabs)


345-345: Hard tabs
Column: 1

(MD010, no-hard-tabs)


347-347: Hard tabs
Column: 1

(MD010, no-hard-tabs)


348-348: Hard tabs
Column: 1

(MD010, no-hard-tabs)


349-349: Hard tabs
Column: 1

(MD010, no-hard-tabs)


350-350: Hard tabs
Column: 1

(MD010, no-hard-tabs)


352-352: Hard tabs
Column: 1

(MD010, no-hard-tabs)


353-353: Hard tabs
Column: 1

(MD010, no-hard-tabs)


354-354: Hard tabs
Column: 1

(MD010, no-hard-tabs)


355-355: Hard tabs
Column: 1

(MD010, no-hard-tabs)


357-357: Hard tabs
Column: 1

(MD010, no-hard-tabs)


358-358: Hard tabs
Column: 1

(MD010, no-hard-tabs)


359-359: Hard tabs
Column: 1

(MD010, no-hard-tabs)


360-360: Hard tabs
Column: 1

(MD010, no-hard-tabs)


362-362: Hard tabs
Column: 1

(MD010, no-hard-tabs)


363-363: Hard tabs
Column: 1

(MD010, no-hard-tabs)


364-364: Hard tabs
Column: 1

(MD010, no-hard-tabs)


365-365: Hard tabs
Column: 1

(MD010, no-hard-tabs)


366-366: Hard tabs
Column: 1

(MD010, no-hard-tabs)


367-367: Hard tabs
Column: 1

(MD010, no-hard-tabs)


368-368: Hard tabs
Column: 1

(MD010, no-hard-tabs)


370-370: Hard tabs
Column: 1

(MD010, no-hard-tabs)


371-371: Hard tabs
Column: 1

(MD010, no-hard-tabs)


372-372: Hard tabs
Column: 1

(MD010, no-hard-tabs)


373-373: Hard tabs
Column: 1

(MD010, no-hard-tabs)


374-374: Hard tabs
Column: 1

(MD010, no-hard-tabs)


375-375: Hard tabs
Column: 1

(MD010, no-hard-tabs)


377-377: Hard tabs
Column: 1

(MD010, no-hard-tabs)


378-378: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (12)
models.go (4)

3-9: LGTM!

Import additions are appropriate for the new Date type and MMDB integration.


11-32: LGTM!

The Date type implementation is well-designed:

  • Correctly handles empty strings by returning early without error
  • Uses time.DateOnly format for parsing ISO 8601 date strings
  • Provides meaningful error wrapping with the original date value for debugging

34-40: LGTM!

The MarshalJSON implementation correctly handles:

  • Zero dates serialized as null
  • Non-zero dates formatted as ISO date strings

The //nolint:unparam comment appropriately acknowledges the unused error return required by json.Marshaler.


558-606: LGTM!

The AnonymousPlus struct and HasData() method are well-implemented:

  • Field documentation is comprehensive and matches the PR objectives
  • HasData() correctly excludes Network and IPAddress fields, consistent with other struct implementations in this file
  • All new Anonymous Plus-specific fields (AnonymizerConfidence, ProviderName, NetworkLastSeen) are included in the data presence check
README.md (1)

324-380: LGTM!

The Anonymous Plus Database documentation section is comprehensive and follows the established pattern of other database examples. It correctly demonstrates:

  • Opening the database
  • Querying with AnonymousPlus()
  • Checking HasData() before accessing fields
  • Accessing both standard anonymous IP flags and Anonymous Plus-specific fields
  • Safe handling of NetworkLastSeen with IsZero() check

The static analysis warnings about hard tabs are false positives - Go code conventionally uses tabs for indentation.

CHANGELOG.md (1)

3-7: LGTM!

The changelog entry accurately documents the new Anonymous Plus database support with appropriate version bump to 2.1.0.

reader_test.go (3)

8-8: LGTM!

Import addition for time package is appropriate for the date assertions in the new test.


169-221: Comprehensive test coverage, but blocked by missing test database file.

The test implementation is thorough and covers:

  • Full data IP with all fields validated (lines 174-198)
  • Minimal data IP scenario (lines 200-210)
  • Private IP returning empty data with Network/IPAddress still set (lines 212-220)

However, this test fails due to the same missing database file GeoIP-Anonymous-Plus-Test.mmdb flagged in example_test.go. The test file needs to be added to the test-data submodule.


487-499: LGTM!

Properly extends TestAllStructsHaveHasData to include AnonymousPlus, ensuring the new struct is covered by the existing test pattern for HasData() method validation.

reader.go (3)

65-65: LGTM! Database type constant follows the established pattern.

The addition of isAnonymousPlus using the bitwise left shift pattern is consistent with other database type constants.


262-278: LGTM! Implementation follows established patterns perfectly.

The AnonymousPlus() method correctly mirrors the implementation of other lookup methods in the codebase:

  • Validates database type with proper error handling
  • Performs MMDB lookup and decoding
  • Populates IPAddress and Network fields from the lookup result
  • Returns the structured result or error

The implementation is consistent with AnonymousIP() and other similar methods.


150-151: No changes needed. The database type string "GeoIP-Anonymous-Plus" correctly matches MaxMind's official GeoIP Anonymous Plus database naming conventions. The implementation properly reads the DatabaseType metadata field from the MMDB file, and the string is consistent with all MaxMind client library examples and test data in the repository.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for the GeoIP Anonymous Plus database, which enhances anonymous IP detection by providing VPN confidence scoring, provider identification, and network activity temporal tracking capabilities.

Key changes:

  • Introduces AnonymousPlus struct with extended fields including confidence scores, provider names, and last-seen dates
  • Implements custom Date type for parsing ISO 8601 date strings from the database
  • Adds comprehensive test coverage for the new functionality

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test-data Updates test data submodule commit reference to include Anonymous Plus test database
reader.go Adds AnonymousPlus() method and database type detection for GeoIP-Anonymous-Plus
models.go Defines Date type with custom unmarshaling and AnonymousPlus struct with all relevant fields
reader_test.go Adds comprehensive test coverage for AnonymousPlus() method including full data, minimal data, and private IP scenarios
example_test.go Provides usage example demonstrating Anonymous Plus database query
README.md Documents Anonymous Plus database integration with code example
CHANGELOG.md Records the new feature addition in version 2.1.0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@oschwald oschwald merged commit 5485595 into main Dec 22, 2025
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants