msisdn

package module
v0.0.0-...-9a8bda3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 7 Imported by: 0

README

go-msisdn

A developer-friendly phone number toolkit for Go — parsing, validation, normalization, formatting, type detection, and telecom operator lookup, built around a single, clean Phone type. Designed for backend systems, fintech, CRM, telecom, and iGaming platforms where you need more than a regex to know whether +254 (712)-345-678 is a real, dialable Safaricom number.

phone, err := msisdn.Parse("0712345678", "KE")
// phone.E164()      -> "+254712345678"
// phone.Country()   -> "Kenya"
// phone.Operator()  -> "Safaricom"
// phone.IsMobile()  -> true

Why go-msisdn

Most Go "phone validators" are a regex and a prayer. go-msisdn instead gives you:

  • A single Phone value type that carries country, national number, validity, number type, and operator — instead of scattering that logic across your codebase.
  • Structured validation results, not just true/false — you get why a number is invalid (wrong length, unrecognized prefix, unknown country code, ...).
  • Configurable, data-driven operator detection — adding a country or operator is a data change, never a code change.
  • First-class JSON and database/sql supportPhone fields on your structs just work with Postgres/MySQL/SQLite and with encoding/json.
  • Batch helpers for validating, normalizing, and parsing lists of numbers, which is most of what you actually do with phone numbers in production.
A note on libphonenumber

The original design for this library called for building on top of Google's libphonenumber via a Go port (e.g. nyaruka/phonenumbers). This build was produced in a network-sandboxed environment that could not reach the Go module proxy or golang.org, so it ships instead with a self-contained, zero-dependency parsing and validation engine with detailed metadata for its "deep" countries (see below) and lighter-weight support for ~50 more.

The engine sits behind the same Phone API a libphonenumber-backed implementation would expose, and the parsing/validation/formatting logic is isolated in a small number of files (parse.go, validate.go, format.go, metadata.go, countries.go) so that swapping in a real libphonenumber binding later — for full global coverage — is a contained, backwards-compatible change and does not touch the public API.


Installation

go get https://github.com/Felloh-254/go-msisdn.git

Requires Go 1.21+. Zero external dependencies.


Quick Start

package main

import (
	"fmt"

	"github.com/Felloh-254/go-msisdn"
)

func main() {
	phone, err := msisdn.Parse("+254712345678", "")
	if err != nil {
		panic(err)
	}
	fmt.Println(phone.E164()) // +254712345678
}

API Examples

Parsing
phone, err := msisdn.Parse("0712345678", "KE")
if err != nil {
	// err is only returned for structural problems: empty input, unknown
	// calling code, or a missing/unsupported region for a non-"+" number.
}

phone.Country()        // "Kenya"
phone.ISO()             // "KE"
phone.CountryCode()     // 254
phone.NationalNumber()  // 712345678
phone.E164()            // "+254712345678"
phone.International()   // "+254 712 345678"
phone.National()        // "0712 345678"
phone.IsValid()          // true
phone.Type()             // msisdn.Mobile
phone.Operator()         // "Safaricom"

A number that parses but doesn't validate (wrong length, unrecognized prefix) is still returned — Parse doesn't error out on that, because "is this number valid" and "could I even make sense of this input" are different questions:

phone, _ := msisdn.Parse("07123", "KE") // too short
phone.IsValid()        // false
phone.InvalidReason()  // "invalid length for Kenya: got 5 digits, expected [9]"
Validation
result := msisdn.Validate("07123", "KE")
// result.Valid    -> false
// result.Possible -> true (right ballpark, just short)
// result.Reason   -> "invalid length for Kenya: got 5 digits, expected [9]"
// result.Code     -> "INVALID_LENGTH"

phone, _ := msisdn.Parse("0712345678", "KE")
phone.IsValid()    // true
phone.IsPossible() // true

Validate never returns a Go error — a structurally unparseable input (empty string, missing region, unknown calling code) is reported as an invalid ValidationResult instead, so it's safe to use directly for form/API input validation.

Normalization
msisdn.Normalize("0712345678", "KE")    // "254712345678", nil
msisdn.Normalize("712345678", "KE")     // "254712345678", nil
msisdn.Normalize("+254712345678", "")   // "254712345678", nil
msisdn.Normalize("254712345678", "KE")  // "254712345678", nil
Formatting
msisdn.Format("0712345678", "KE", msisdn.E164)         // "+254712345678"
msisdn.Format("0712345678", "KE", msisdn.National)      // "0712 345678"
msisdn.Format("0712345678", "KE", msisdn.International) // "+254 712 345678"
msisdn.Format("0712345678", "KE", msisdn.RFC3966)       // "tel:+254712345678"

// Or on an already-parsed Phone:
phone.Format(msisdn.National) // "0712 345678"

Naming note: the design brief asked for Format(number, E164) / Format(number, NATIONAL). Go doesn't allow a type and a top-level function to share an identifier, so the type is msisdn.Style while the constants (E164, National, International, RFC3966) and the top-level Format function keep exactly the requested call shape: msisdn.Format(number, region, msisdn.National).

Cleaning
msisdn.Clean("+254 (712)-345-678") // "254712345678"
Comparison
msisdn.Equal("0712345678", "254712345678", "KE")  // true
msisdn.Equal("0712345678", "+254712345678", "KE")  // true
msisdn.Equal("0712345678", "0722345678", "KE")     // false

phone1.Equal(phone2) // compare two already-parsed *Phone values
Masking
msisdn.Mask("254712345678") // "2547******78"

msisdn.Mask("254712345678",
	msisdn.WithPrefixVisible(6),
	msisdn.WithSuffixVisible(2),
) // "254712****78"

phone.Mask() // mask an already-parsed Phone's E.164 form
Country metadata
phone.Country()     // "Kenya"
phone.ISO()          // "KE"
phone.CountryCode()  // 254
Number type detection
phone.Type()             // msisdn.Mobile
phone.IsMobile()          // true
phone.IsFixedLine()       // false
phone.IsTollFree()        // false
phone.IsPremiumRate()     // false
phone.IsVoIP()            // false
phone.IsPager()           // false

Recognized types: Mobile, FixedLine, FixedLineOrMobile, TollFree, PremiumRate, VoIP, Pager, Unknown.

Operator detection
phone, _ := msisdn.Parse("0712345678", "KE")
phone.Operator() // "Safaricom"

Operator lookup is entirely data-driven (see operators/) — a longest-prefix match against a per-country table registered via operators.RegisterCountry. Adding a country or a new operator prefix is a small, self-contained data file, not a change to any lookup logic:

operators.RegisterCountry("XX", []operators.Rule{
	{Operator: "ExampleTel", Prefixes: []string{"70", "71"}},
})
Local conversion
msisdn.ToLocal("+254712345678", "") // "0712345678", nil
phone.Local()                        // "0712345678"
Deduplication
msisdn.Dedupe([]string{
	"0712345678",
	"+254712345678",
	"254712345678",
}, "KE")
// []string{"+254712345678"}
Batch processing
numbers := []string{"0712345678", "not-a-number", "0771234567"}

msisdn.ParseMany(numbers, "KE")     // []ParseResult
msisdn.ValidateMany(numbers, "KE")  // []ValidationResult
msisdn.NormalizeMany(numbers, "KE") // []NormalizeResult

Every result is keyed to its input by index, and per-item failures never abort the batch.

Example numbers (for tests & fixtures)
msisdn.Example("KE") // "+254712345678", nil
msisdn.ExamplePhone("NG") // *Phone, nil
Database support
type User struct {
	ID    int
	Phone msisdn.Phone
}

_, err := db.Exec(`INSERT INTO users (id, phone) VALUES ($1, $2)`, user.ID, user.Phone)

var u User
err = db.QueryRow(`SELECT id, phone FROM users WHERE id = $1`, id).Scan(&u.ID, &u.Phone)

Phone implements driver.Valuer (stores as E.164 text) and sql.Scanner (parses E.164 text back into a Phone), so it works naturally as a column type against PostgreSQL, MySQL, and SQLite text/varchar columns.

JSON support
type User struct {
	Name  string      `json:"name"`
	Phone msisdn.Phone `json:"phone"`
}

data := []byte(`{"name":"Wanjiru","phone":"+254712345678"}`)
var u User
json.Unmarshal(data, &u)
u.Phone.E164() // "+254712345678"

json.Marshal(u) // {"name":"Wanjiru","phone":"+254712345678"}

Supported Countries

Deep support (full length + number-type + operator detection)
Country ISO Calling code Operators
Kenya KE 254 Safaricom, Airtel Kenya, Telkom Kenya, Equitel
Uganda UG 256 MTN Uganda, Airtel Uganda, Africell Uganda, UTL
Tanzania TZ 255 Vodacom, Airtel, Yas (Tigo), Halotel, TTCL
Rwanda RW 250 MTN Rwanda, Airtel Rwanda
Nigeria NG 234 MTN, Airtel, Globacom, 9mobile
Ghana GH 233 MTN Ghana, Vodafone Ghana, AirtelTigo
Zambia ZM 260 MTN Zambia, Airtel Zambia, Zamtel
Shallow support (name, calling code, E.164/national formatting)

United States, Canada, United Kingdom, Ireland, France, Germany, Spain, Portugal, Italy, Netherlands, Belgium, Switzerland, Sweden, Norway, Denmark, Finland, Poland, Austria, Greece, South Africa, Egypt, Morocco, Ethiopia, Algeria, Tunisia, Côte d'Ivoire, Senegal, Cameroon, India, Pakistan, Bangladesh, China, Japan, South Korea, Indonesia, Philippines, Vietnam, Thailand, Malaysia, Singapore, Australia, New Zealand, Brazil, Mexico, Argentina, Colombia, Peru, Chile, Russia, Turkey, Saudi Arabia, United Arab Emirates, Israel, Qatar.

IsValid()/Type() are best-effort for shallow countries: length is checked where the rule is simple and well known, but number-type/operator prefix ranges aren't populated. Adding deep support for any of these (or a new country entirely) means adding an entry to countries.go — see Extending below.

Known limitation: fixed-line prefix ranges for the deep countries are illustrative, not exhaustive. Mobile ranges (the primary use case for OTP/fintech/ iGaming workloads) are the focus of this build.


Supported Features

  • Parsing (Parse, ParseMany)
  • Validation with structured reasons (Validate, ValidateMany, IsValid, IsPossible)
  • Normalization (Normalize, NormalizeMany)
  • Multi-format output (Format, E164, National, International, RFC3966)
  • Cleaning (Clean)
  • Comparison (Equal)
  • Configurable masking (Mask, WithPrefixVisible, WithSuffixVisible, WithMaskChar)
  • Country metadata (Country, ISO, CountryCode)
  • Number type detection (Type, IsMobile, IsFixedLine, IsTollFree, IsPremiumRate, IsVoIP, IsPager)
  • Data-driven operator detection (Operator, operators.RegisterCountry)
  • Local conversion (ToLocal, Phone.Local)
  • Deduplication (Dedupe)
  • Batch processing (ParseMany, ValidateMany, NormalizeMany)
  • Example number generator (Example, ExamplePhone)
  • database/sql Scanner/Valuer
  • encoding/json Marshaler/Unmarshaler

Architecture

go-msisdn/
├── go.mod
├── LICENSE
├── README.md
├── *.go                 # root "msisdn" package: Phone type, Parse, Validate,
│                         # Normalize, Format, Mask, batch helpers, JSON/DB support
├── errors/               # sentinel errors + structured ValidationError
│   └── errors.go
├── operators/             # data-driven operator (MNO) prefix lookup
│   ├── operators.go        # registry + longest-prefix-match Lookup
│   ├── kenya.go, uganda.go, tanzania.go, rwanda.go,
│   │   nigeria.go, ghana.go, zambia.go     # per-country data, each an init()
│   └── operators_test.go
├── examples/
│   └── basic/main.go       # runnable end-to-end example (`go run ./examples/basic`)
├── msisdn_test.go          # table-driven core tests
└── msisdn_edge_test.go     # edge cases
Why this layout, and not cmd/, internal/, pkg/

The brief's suggested layout (cmd/, internal/, pkg/, plus one subpackage per concern: parser/, validator/, formatter/, types/) is a common enterprise-Java- style convention, but it's explicitly discouraged for libraries by the Go community (there's no cmd/ because this is a library, not a binary; wrapping everything in pkg/ adds a directory level with no semantic value; and splitting parser/ validator/formatter/types into separate packages just to keep a single cohesive concept — "a phone number" — apart invites import cycles, since a parser needs the Phone type, the formatter needs the parser's output, and the validator needs both).

Instead:

  • The root package (msisdn) owns the Phone type and everything that operates directly on it (parsing, validation, formatting, normalization, masking, batching, JSON, DB). This is idiomatic for a focused Go library — see how time, net/url, or net/mail are structured: one cohesive package, split across files by concern, not by artificial package boundaries.
  • operators/ is a genuinely separate concern (it doesn't need to know about parsing or formatting — just "given a country and a prefix, what operator?") and is explicitly meant to be user-extensible, so it's a real subpackage with its own registry.
  • errors/ is a real subpackage because sentinel errors are meant to be a stable, independently-importable contract (errors.Is(err, msisdnerrors.ErrInvalidLength)) that shouldn't force importing the whole library.
  • examples/ holds a runnable example program rather than living under cmd/ (which implies "this repo produces a CLI binary", which it doesn't).
  • Tests live next to the code they test (msisdn_test.go, operators_test.go), which is the standard Go convention — there's no separate tests/ directory, since Go's tooling (go test ./...) doesn't need one and a parallel test tree would just drift out of sync with the code.
Public API design
  • One entry type, Phone. Everything you'd want to know about a number hangs off one value, returned by Parse. No separate "metadata" object to keep in sync.
  • Package-level functions for one-shot use (Normalize, Clean, Equal, Mask, Format, Validate, Dedupe) so simple call sites don't need to hold onto a Phone at all — mirroring how strings and path/filepath are used.
  • Errors vs. invalidity are different things. Parse returns a Go error only when it structurally cannot make sense of the input (empty string, unrecognized country code, missing region). A number that parses but fails business-rule validation (wrong length, unknown prefix) is returned successfully, with IsValid()/ IsPossible()/InvalidReason() telling you why. This mirrors how you'd want to handle user-submitted numbers in a signup form: you don't want a Go error (and the awkward if err != nil branching that implies) for "this looks like a phone number but it's the wrong length" — you want a value you can inspect and show a message for.
  • Structured error codes (errors.Code, e.g. "INVALID_LENGTH") sit alongside human-readable reasons, so API responses can switch on a stable code while logs get a readable message.
  • Functional options for Mask (WithPrefixVisible, ...) rather than a config struct, since masking has a good, unsurprising default and options are the idiomatic way to make optional knobs discoverable via autocomplete without an explosion of MaskN function variants.
  • Nil-safe methods on *Phone. Every getter is safe to call on a nil *Phone (returns zero values), so partially-initialized state (e.g. a struct field that hasn't been parsed yet) doesn't panic when read.

Testing

go test ./...
go test ./... -cover
go test ./... -v

Tests are table-driven throughout and cover: Kenyan/Ugandan/Tanzanian/Rwandan/Nigerian/ Ghanaian/Zambian numbers, invalid and impossible numbers, every input format (E.164, "00" international prefix, national with/without trunk zero, punctuated), country and operator detection, number-type classification, JSON round-tripping, database/sql Scan/Value, nil-safety, and shallow-country fallback behavior.


Extending the Library

Add an operator (existing country):

operators.RegisterCountry("KE", append(operators.SupportedCountries() /* ... */))
// or simply add a new Rule to operators/kenya.go

Add a new country with full support: add a countryMeta entry to registerDeepCountries() in countries.go (calling code, trunk prefix, valid NSN lengths, per-type prefix ranges) and, if you want operator detection, a new file under operators/ calling operators.RegisterCountry in an init().

Add shallow support for a new country: append a row to the list in registerShallowCountries() in countries.go.

No existing code needs to change in either case — the registries are additive.


Contributing

  1. Fork the repo and create a feature branch.
  2. Keep the "deep vs. shallow" split in mind: if you're adding real validation depth for a country, prefer accuracy over exhaustiveness — cite a source for prefix ranges in your PR description where possible.
  3. Add table-driven tests for anything you change; go test ./... -cover should not regress.
  4. Run gofmt -l . and go vet ./... before opening a PR.
  5. Open a PR describing the change and, for new countries/operators, the source of the numbering data.

License

MIT — see LICENSE.

Documentation

Overview

Package msisdn is a developer-friendly phone number toolkit for backend systems, fintech, CRM, telecom, and iGaming platforms: parsing, validation, normalization, formatting, type/operator detection, masking, and batch helpers, built around a single Phone value type.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Clean

func Clean(number string) string

Clean strips every character that is not an ASCII digit, including any leading "+". It does not parse or validate the result -- it's a pure text-cleaning utility, useful as a first pass before Parse, or for sanitizing free-text input.

func Dedupe

func Dedupe(numbers []string, defaultRegion string) []string

Dedupe normalizes every number in numbers (using defaultRegion for any that lack an explicit country code) and returns the unique E.164 forms, preserving the order of first appearance. Numbers that fail to parse are silently skipped -- use ValidateMany first if you need to know which inputs were dropped and why.

func Equal

func Equal(number1, number2 string, defaultRegion ...string) bool

Equal reports whether two phone number strings refer to the same number, regardless of how each is formatted (local, E.164, spaced, punctuated, ...). An optional defaultRegion is used for either input that doesn't carry an explicit "+"/"00" country code, exactly as with Parse; if omitted, such inputs are compared as invalid (since they can't be unambiguously resolved to a country).

func Example

func Example(iso string) (string, error)

Example returns a realistic sample E.164 phone number for the given ISO-3166-1 alpha-2 country, suitable for tests, fixtures, and demo data. It returns an error if the country isn't registered or has no example number configured.

n, _ := msisdn.Example("KE") // "+254712345678"

func Format

func Format(number, region string, style Style) (string, error)

Format parses number (optionally using region as the default country when number has no leading "+") and renders it using the requested Style. It's a convenience wrapper around Parse + Phone.Format for callers who don't need to keep the parsed Phone around.

msisdn.Format("0712345678", "KE", msisdn.E164) // "+254712345678"

func Mask

func Mask(number string, opts ...MaskOption) string

Mask redacts the middle of a phone number for privacy-friendly logging, keeping a configurable number of leading and trailing digits visible. It operates on digits only (any "+", spaces, or punctuation in number are stripped first, matching Clean) and does not require the number to be valid or even parseable -- it's a text transform, not a parser.

msisdn.Mask("254712345678")                          // "2547******78"
msisdn.Mask("254712345678", msisdn.WithPrefixVisible(6)) // "254712****78"

func Normalize

func Normalize(number, region string) (string, error)

Normalize parses number (using region as the default country for numbers without a leading "+"/"00") and returns it as a bare digit string of calling-code + national number, with no "+", spaces, or other punctuation -- e.g. "254712345678". This is the canonical MSISDN form used as a storage/lookup key throughout telecom and fintech systems.

func SupportedCountries

func SupportedCountries() []string

SupportedCountries returns the ISO codes of every registered country, including both "deep" (fully validated) and "shallow" (name/calling code only) entries.

func ToLocal

func ToLocal(number, region string) (string, error)

ToLocal converts a number back into the domestic/local dialling form (trunk prefix + national number, no country code), e.g. "254712345678" -> "0712345678". It's the inverse of prefixing a local number with a country code.

Types

type MaskOption

type MaskOption func(*maskConfig)

MaskOption customizes Mask's behavior. See WithPrefixVisible, WithSuffixVisible, and WithMaskChar.

func WithMaskChar

func WithMaskChar(r rune) MaskOption

WithMaskChar sets the character used to replace hidden digits. Default '*'.

func WithPrefixVisible

func WithPrefixVisible(n int) MaskOption

WithPrefixVisible sets how many leading digits stay visible. Default 4.

func WithSuffixVisible

func WithSuffixVisible(n int) MaskOption

WithSuffixVisible sets how many trailing digits stay visible. Default 2.

type NormalizeResult

type NormalizeResult struct {
	Input        string `json:"input"`
	Normalized   string `json:"normalized,omitempty"`
	Error        error  `json:"-"`
	ErrorMessage string `json:"error,omitempty"`
}

NormalizeResult pairs a batch input with its normalized form (or error).

func NormalizeMany

func NormalizeMany(numbers []string, defaultRegion string) []NormalizeResult

NormalizeMany runs Normalize over every number in numbers against defaultRegion.

type NumberType

type NumberType int

NumberType classifies what kind of line a phone number belongs to.

const (
	// Unknown means go-msisdn could not determine the number type, most
	// often because the country only has shallow support.
	Unknown NumberType = iota
	// Mobile is a mobile/cellular number.
	Mobile
	// FixedLine is a landline number.
	FixedLine
	// FixedLineOrMobile is used when the numbering range is shared
	// between mobile and fixed-line allocations and cannot be told
	// apart from the digits alone.
	FixedLineOrMobile
	// TollFree is a toll-free (freephone) number.
	TollFree
	// PremiumRate is a premium-rate number.
	PremiumRate
	// VoIP is a voice-over-IP number.
	VoIP
	// Pager is a pager number.
	Pager
)

func (NumberType) MarshalJSON

func (t NumberType) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler so NumberType serializes as its string name (e.g. "MOBILE") rather than a bare integer.

func (NumberType) String

func (t NumberType) String() string

String implements fmt.Stringer.

type ParseResult

type ParseResult struct {
	Input string `json:"input"`
	Phone *Phone `json:"phone,omitempty"`
	Error error  `json:"-"`
	// ErrorMessage mirrors Error as a string for JSON consumers.
	ErrorMessage string `json:"error,omitempty"`
}

ParseResult pairs a batch input with its parse outcome, so results can always be matched back to the original input by index -- important because Parse itself can fail per-item without aborting the batch.

func ParseMany

func ParseMany(numbers []string, defaultRegion string) []ParseResult

ParseMany parses every number in numbers against defaultRegion, never stopping at the first failure. Check each result's Error/ErrorMessage to see which inputs failed and why.

type Phone

type Phone struct {
	// contains filtered or unexported fields
}

Phone represents a parsed phone number together with everything go-msisdn was able to determine about it: its country, national significant number, validity, type, and (where available) operator.

Phone is immutable and safe for concurrent use. The zero value is not usable; construct a Phone via Parse, ParseMany, or by decoding JSON / scanning from a database column.

func ExamplePhone

func ExamplePhone(iso string) (*Phone, error)

ExamplePhone is like Example but returns a parsed *Phone.

func Parse

func Parse(raw, region string) (*Phone, error)

Parse parses raw into a Phone.

If raw begins with "+" or "00" it is treated as already carrying an explicit country calling code and region is ignored. Otherwise region must be a supported ISO-3166-1 alpha-2 code (e.g. "KE") and raw is interpreted as a national/local number, with any domestic trunk prefix (e.g. a leading "0") stripped automatically.

Parse returns an error only for structural problems: an empty input, an unrecognized calling code, a missing/unknown region for a non-"+" number, or input that doesn't resemble a phone number at all. A number that parses structurally but fails validation (wrong length, unknown prefix range) is still returned, with Phone.IsValid() reporting false and Phone.InvalidReason() explaining why -- callers that want a hard error for invalid-but-parseable numbers should check IsValid() themselves, or use Validate.

func (*Phone) Country

func (p *Phone) Country() string

Country returns the country's display name, e.g. "Kenya". Returns "" for a zero-value Phone.

func (*Phone) CountryCode

func (p *Phone) CountryCode() int

CountryCode returns the E.164 country calling code, e.g. 254.

func (*Phone) E164

func (p *Phone) E164() string

E164 renders the number as "+<callingcode><nationalnumber>", e.g. "+254712345678". This is the canonical, comparison-safe form.

func (*Phone) Equal

func (p *Phone) Equal(other *Phone) bool

Equal reports whether two Phone values refer to the same number, compared by E.164 form.

func (*Phone) Format

func (p *Phone) Format(style Style) string

Format renders the phone number using the requested Style.

func (*Phone) ISO

func (p *Phone) ISO() string

ISO returns the ISO-3166-1 alpha-2 region code, e.g. "KE".

func (*Phone) International

func (p *Phone) International() string

International renders the number as "+<callingcode> <spaced national>", e.g. "+254 712 345678".

func (*Phone) InvalidReason

func (p *Phone) InvalidReason() string

InvalidReason returns a human-readable explanation of why the number is invalid, or "" if it is valid.

func (*Phone) IsFixedLine

func (p *Phone) IsFixedLine() bool

IsFixedLine reports whether Type() is FixedLine or FixedLineOrMobile.

func (*Phone) IsMobile

func (p *Phone) IsMobile() bool

IsMobile reports whether Type() is Mobile or FixedLineOrMobile.

func (*Phone) IsPager

func (p *Phone) IsPager() bool

IsPager reports whether Type() is Pager.

func (*Phone) IsPossible

func (p *Phone) IsPossible() bool

IsPossible reports whether the number could plausibly be dialable -- i.e. it is not off by an implausible margin in length -- even if it isn't fully Valid (for example, correct length but an unrecognized prefix range). Every Valid number is also Possible.

func (*Phone) IsPremiumRate

func (p *Phone) IsPremiumRate() bool

IsPremiumRate reports whether Type() is PremiumRate.

func (*Phone) IsTollFree

func (p *Phone) IsTollFree() bool

IsTollFree reports whether Type() is TollFree.

func (*Phone) IsValid

func (p *Phone) IsValid() bool

IsValid reports whether the number is fully valid: known country, correct length, and (for deeply-supported countries) a recognized number-type prefix range.

func (*Phone) IsVoIP

func (p *Phone) IsVoIP() bool

IsVoIP reports whether Type() is VoIP.

func (*Phone) Local

func (p *Phone) Local() string

Local returns the domestic dialling form without cosmetic spacing, e.g. "0712345678". This is what the project brief calls "local conversion": turning an international number back into the form a subscriber would dial domestically.

func (Phone) MarshalJSON

func (p Phone) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler, encoding the Phone as its E.164 string, e.g. "+254712345678". A zero-value Phone marshals to null.

func (*Phone) Mask

func (p *Phone) Mask(opts ...MaskOption) string

Mask redacts the middle of the parsed number's E.164 digits. See the package-level Mask function for option documentation.

func (*Phone) National

func (p *Phone) National() string

National renders the number in domestic dialling form, e.g. "0712 345678". If the country has no trunk prefix (e.g. NANP countries) this is the same as the spaced national significant number.

func (*Phone) NationalNumber

func (p *Phone) NationalNumber() uint64

NationalNumber returns the national significant number (no trunk prefix, no country code) as an unsigned integer, e.g. 712345678. Use NationalNumberString if you need to preserve leading zeros (rare, but possible for some countries' number ranges).

func (*Phone) NationalNumberString

func (p *Phone) NationalNumberString() string

NationalNumberString returns the national significant number as a digit string, preserving any leading zeros.

func (*Phone) Operator

func (p *Phone) Operator() string

Operator returns the detected mobile network operator name, e.g. "Safaricom", or "" if unknown (either because the country has no operator table registered, or no prefix rule matched).

func (*Phone) RFC3966

func (p *Phone) RFC3966() string

RFC3966 renders the number as a "tel:" URI, e.g. "tel:+254712345678".

func (*Phone) Raw

func (p *Phone) Raw() string

Raw returns the exact string that was originally passed to Parse.

func (*Phone) Scan

func (p *Phone) Scan(src interface{}) error

Scan implements sql.Scanner, so a Phone (or *Phone) field on a struct can be populated directly from a database column via database/sql. The column value is parsed with Parse using "" as the region, so it must already be in E.164 form ("+254712345678") -- exactly what Value produces, which is what makes the pair round-trip safe.

func (*Phone) String

func (p *Phone) String() string

String implements fmt.Stringer, returning the E.164 form.

func (*Phone) Type

func (p *Phone) Type() NumberType

Type classifies the number (mobile, fixed line, toll free, ...). It returns Unknown for countries go-msisdn only shallowly supports, or if the number's prefix doesn't fall into a known range.

func (*Phone) UnmarshalJSON

func (p *Phone) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler. It accepts a JSON string containing an E.164 number (e.g. "+254712345678"); a plain national number without a region hint cannot be unambiguously resolved from JSON alone and will produce an error. null decodes to the zero Phone.

type User struct {
    Phone msisdn.Phone `json:"phone"`
}

func (Phone) Value

func (p Phone) Value() (driver.Value, error)

Value implements driver.Valuer, storing the number as its E.164 string so it round-trips cleanly through PostgreSQL, MySQL, and SQLite text/ varchar columns.

type Style

type Style int

Style identifies an output representation for a phone number.

Note on naming: the initial design brief called this type "Format" and asked for a top-level Format(number, STYLE) function. Go does not allow a type and a function to share an identifier in the same package, so the type is named Style here and the top-level function keeps the name Format -- exactly matching the requested call shape, msisdn.Format(n, msisdn.National), while staying compilable.

const (
	// E164 is the "+254712345678" form: a plus sign, calling code, and
	// national significant number, with no other characters.
	E164 Style = iota
	// National is the domestic dialling form, e.g. "0712 345678".
	National
	// International is the E.164 digits with human-friendly spacing and
	// a leading "+", e.g. "+254 712 345678".
	International
	// RFC3966 is the "tel:+254712345678" URI form.
	RFC3966
)

func (Style) String

func (s Style) String() string

type ValidationResult

type ValidationResult struct {
	// Valid is true if the number is fully valid.
	Valid bool `json:"valid"`
	// Possible is true if the number is at least plausible (right
	// ballpark length), even if not fully Valid.
	Possible bool `json:"possible"`
	// Reason is a human-readable explanation, empty when Valid is true.
	Reason string `json:"reason,omitempty"`
	// Code is a stable, machine-readable reason code, empty when Valid
	// is true. See the errors package for possible values.
	Code msisdnerrors.Code `json:"code,omitempty"`
	// Phone is the parsed number, or nil if parsing itself failed
	// (e.g. empty input, unrecognized calling code, missing region).
	Phone *Phone `json:"-"`
}

ValidationResult is the structured outcome of Validate: it tells you not just whether a number is valid, but why not.

func Validate

func Validate(number, region string) ValidationResult

Validate parses number (see Parse for how region is used) and reports detailed validation information. Unlike Parse, Validate never returns a Go error -- structural parse failures are reported as an invalid ValidationResult instead, so this is the simplest entry point for "is this number OK, and if not, why?" checks such as form validation.

func ValidateMany

func ValidateMany(numbers []string, defaultRegion string) []ValidationResult

ValidateMany runs Validate over every number in numbers against defaultRegion.

Directories

Path Synopsis
Package errors defines the sentinel errors and structured error codes used throughout go-msisdn.
Package errors defines the sentinel errors and structured error codes used throughout go-msisdn.
examples
basic command
Command basic demonstrates the core go-msisdn API end to end.
Command basic demonstrates the core go-msisdn API end to end.
Package operators provides a configurable, data-driven mobile network operator (MNO) lookup keyed by ISO-3166 country code and national significant number prefix.
Package operators provides a configurable, data-driven mobile network operator (MNO) lookup keyed by ISO-3166 country code and national significant number prefix.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL