An open API service indexing awesome lists of open source software.

https://github.com/randomseed-io/phone-number

Phone numbers as data: validate, inspect, search, generate.
https://github.com/randomseed-io/phone-number

carrier clojure geolocation i18n metadata phone phone-number phonenumber timezone validation

Last synced: 9 days ago
JSON representation

Phone numbers as data: validate, inspect, search, generate.

Awesome Lists containing this project

README

          

# Phone Number

*Phone numbers as data: validate, inspect, search, generate.*

[![Phone-number on Clojars](https://img.shields.io/clojars/v/io.randomseed/phone-number.svg)](https://clojars.org/io.randomseed/phone-number)
[![Phone-number on cljdoc](https://cljdoc.org/badge/io.randomseed/phone-number)](https://cljdoc.org/d/io.randomseed/phone-number/CURRENT)
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/randomseed-io/phone-number/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/randomseed-io/phone-number/tree/master)

A pragmatic, data-oriented Clojure wrapper around Google's Libphonenumber for
validating, inspecting, formatting, searching text, and generating phone numbers. It
treats phone numbers as data: most functions accept strings, numbers, `PhoneNumber`
instances, or keyword-keyed maps, and return values that are pleasant to pipe
through.

## Features

* Data-first, polymorphic API: phone numbers can be expressed as numbers, strings,
`PhoneNumber` objects, or maps.

* Rich, keyword-keyed info maps with optional namespace inference
(e.g. a key can be `:phone-number/type` or just `:type`;
a type can be `:phone-number.type/mobile` or just `:mobile`).

* Optional enrichment with carrier, location, and time zone data (via libphonenumber add-on
modules), exposed through the same data-first API.

* Stable options-map API for text searching (`phone-number.core/find-numbers-opts`),
backed by lazy maps (LazyMap) so expensive info can be computed on demand.

* Built-in support for short numbers (e.g. emergency numbers): validation, reachability,
and expected cost class.

* Sample generation that returns both the `PhoneNumber` and reproducibility/debug data
(including `:phone-number.sample/random-seed` and sampler stats).

* Specs with generators for generative testing, plus structured errors via `ex-info`
with `:phone-number/error` in `ex-data`.

## Caveats

If your program processes a lot of phone numbers and your strategy is to keep them in
a native format (a result of calling `phone-number.core/number`), then be aware that,
by default, all created `PhoneNumber` objects will have their raw input stored
internally. This affects **comparison** in a way that the object representing
a number `+442920183133` will **not be equal** to the object representing the same
number but with spaces (`+44 2920 183 133`). This is because the equality test is
based, among other things, on raw input values, which are also used to generate each
object's hash code.

To work around this, you have two options:

* Use `phone-number.core/number-noraw` on input data to parse numbers without
preserving raw inputs.

* Use `phone-number.core/number-noraw` on existing objects to create raw-input-free
copies.

Optionally, `phone-number.core/number-optraw` can also be used (especially in
processing pipelines) to preserve raw input only when the created object is
initialized from an existing one (an instance of `PhoneNumber`). For other argument
types, the protocol method behaves like `number-noraw`.

Some functions validate their arguments and may throw `clojure.lang.ExceptionInfo` on
invalid inputs. Such exceptions include `:phone-number/error` in `ex-data`, which can
be used for error classification.

## Sneak peeks

* It **can show information** about phone numbers:

```clojure
(require '[phone-number.core :as phone])

;; region taken from a phone number
;; using system's default locale

(phone/info "+44 29 2018 3133")

{:phone-number/country-code 44,
:phone-number/dialing-region :phone-number.region/gb,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? true,
:phone-number/geographical? true,
:phone-number/location "Cardiff",
:phone-number/possible? true,
:phone-number/region :phone-number.region/gb,
:phone-number/type :phone-number.type/fixed-line,
:phone-number/valid? true,
:phone-number.format/e164 "+442920183133",
:phone-number.format/international "+44 29 2018 3133",
:phone-number.format/national "029 2018 3133",
:phone-number.format/raw-input "+44 29 2018 3133",
:phone-number.format/rfc3966 "tel:+44-29-2018-3133",
:phone-number.tz-format/full-standalone '("Greenwich Mean Time"),
:phone-number.tz-format/id '("Europe/London"),
:phone-number.tz-format/short-standalone '("GMT"),
:phone.number.short/possible? false,
:phone.number.short/valid? false}

;; region passed as an argument
;; locale setting passed as an argument

(phone/info "601 100 601" :pl :pl)

{:phone-number/country-code 48,
:phone-number/carrier "Plus",
:phone-number/dialing-region :phone-number.region/pl,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? true,
:phone-number/geographical? false,
:phone-number/location "Polska",
:phone-number/possible? true,
:phone-number/region :phone-number.region/pl,
:phone-number/type :phone-number.type/mobile,
:phone-number/valid? true,
:phone-number.format/e164 "+48601100601",
:phone-number.format/international "+48 601 100 601",
:phone-number.format/national "601 100 601",
:phone-number.format/raw-input "601 100 601",
:phone-number.format/rfc3966 "tel:+48-601-100-601",
:phone-number.tz-format/full-standalone '("Czas środkowoeuropejski"),
:phone-number.tz-format/id '("Europe/Warsaw"),
:phone-number.tz-format/short-standalone '("CET"),
:phone.number.short/possible? false,
:phone.number.short/valid? false}

(phone/info "8081 570001" :gb :en)

{:phone-number/country-code 44,
:phone-number/dialing-region :phone-number.region/gb,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? true,
:phone-number/geographical? false,
:phone-number/possible? true,
:phone-number/region :phone-number.region/gb,
:phone-number/type :phone-number.type/toll-free,
:phone-number/valid? true,
:phone-number.format/e164 "+448081570001",
:phone-number.format/international "+44 808 157 0001",
:phone-number.format/national "0808 157 0001",
:phone-number.format/raw-input "8081 570001",
:phone-number.format/rfc3966 "tel:+44-808-157-0001",
:phone-number.tz-format/full-standalone '("Greenwich Mean Time" "British Time"),
:phone-number.tz-format/id '("Europe/Guernsey"
"Europe/Isle_of_Man"
"Europe/Jersey"
"Europe/London"),
:phone-number.tz-format/short-standalone '("GMT" "BT"),
:phone.number.short/possible? false,
:phone.number.short/valid? false}
```

* It **validates** phone numbers:

```clojure
(require '[phone-number.core :as phone])

(phone/valid? 8081570001 :gb) ; => true
(phone/valid? "+448081570001") ; => true
(phone/valid? 8081570001 :pl) ; => false
(phone/valid? "8081570001") ; => false

(phone/possible? "8081570001") ; => false
(phone/possible? "8081570001" :gb) ; => true
(phone/possible? "8081570001" :pl) ; => true
```

* It **searches** for phone numbers in text (recommended: the stable options-map API):

```clojure
(require '[phone-number.core :as phone])

(->> (phone/find-numbers-opts
"Call me at +44 808 157 0001!" {:region-code :gb
:leniency :valid
:max-tries 1})
first
(into {}) ;; materialize lazy-map (also forces :phone-number/info)
(dissoc :phone-number/number))

;; If you don't want the info map to be generated at all:
(->> (phone/find-numbers-opts
"Call me at +44 808 157 0001!" {:region-code :gb
:max-tries 1
:locale-specification false})
first
(into {})
(dissoc :phone-number/number))
```

* It gives known phone number **formats** and **types**:

```clojure
(require '[phone-number.core :as phone])

phone/formats

#{:phone-number.format/e164
:phone-number.format/international
:phone-number.format/national
:phone-number.format/raw-input
:phone-number.format/rfc3966}

phone/types

#{:phone-number.type/fixed-line
:phone-number.type/fixed-line-or-mobile
:phone-number.type/mobile
:phone-number.type/pager
:phone-number.type/personal
:phone-number.type/premium-rate
:phone-number.type/shared-cost
:phone-number.type/toll-free
:phone-number.type/uan
:phone-number.type/unknown
:phone-number.type/voicemail
:phone-number.type/voip}
```

* It **generates** phone numbers:

```clojure
(phone/generate)

{:phone-number/info
{:phone-number/country-code 213,
:phone-number/geographical? false,
:phone-number/possible? true,
:phone-number/region :phone-number.region/dz,
:phone-number/type :phone-number.type/unknown,
:phone-number/valid? false,
:phone-number/dialing-region :phone-number.region/dz,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? false,
:phone-number.format/e164 "+213181525997",
:phone-number.format/international "+213 181525997",
:phone-number.format/national "181525997",
:phone-number.format/rfc3966 "tel:+213-181525997",
:phone.number.short/possible? false,
:phone.number.short/valid? false},

:phone-number/number #,
:phone-number.sample/digits ["+213" nil "181525997"],
:phone-number.sample/hits 10,
:phone-number.sample/max-samples 1000,
:phone-number.sample/random-seed 7521527664400716800,
:phone-number.sample/samples 11}

(require [phone-number.spec :as spec]
[clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen])

(gen/generate (s/gen :phone-number/valid))

{:phone-number/info #delay[{:status :pending, :val nil} 0x3810d15d],
:phone-number/number #,
:phone-number.sample/digits ["+7" nil "937627908"],
:phone-number.sample/hits 11,
:phone-number.sample/max-samples 150,
:phone-number.sample/random-seed 7581363778716192180,
:phone-number.sample/samples 27}

(gen/generate (s/gen (s/and :phone-number/possible :phone-number/invalid)))

{:phone-number/info #delay[{:status :pending, :val nil} 0x41c0e225],
:phone-number/number #,
:phone-number.sample/digits ["+84" nil "0270454"],
:phone-number.sample/hits 8,
:phone-number.sample/max-samples 200,
:phone-number.sample/random-seed -9105741821593959780,
:phone-number.sample/samples 12}
```

And more…

## Installation

To use phone-number in your project, add the following to the dependencies section of
`project.clj` or `build.boot`:

```clojure
[io.randomseed/phone-number "3.23-0"]
```

For `deps.edn`, add the following as an element of a map under the `:deps` or
`:extra-deps` key:

```clojure
io.randomseed/phone-number {:mvn/version "3.23-0"}
```

Additionally, if you want to use the specs and generators provided by phone-number,
add the following to your development profile:

```clojure
org.clojure/spec.alpha {:mvn/version "0.6.249"}
org.clojure/test.check {:mvn/version "1.1.3"}
```

You can also download the JAR from [Clojars](https://clojars.org/io.randomseed/phone-number).

## Documentation

Full documentation including usage examples is available at:

* https://randomseed.io/software/phone-number/

## License

Copyright © 2020–2026 Paweł Wilk

Phone-number is copyrighted software owned by Paweł Wilk (pw@gnu.org). You may
redistribute and/or modify this software as long as you comply with the terms of
the [GNU Lesser General Public License][LICENSE] (version 3).

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

## Development

[![CircleCI](https://circleci.com/gh/randomseed-io/phone-number.svg?style=svg)](https://circleci.com/gh/randomseed-io/phone-number)

### Building docs

```bash
make docs
```

### Building JAR

```bash
make jar
```

### Rebuilding POM

```bash
make pom
```

### Signing POM

```bash
make sig
```

### Deploying to Clojars

```bash
make deploy
```

### Interactive development

```bash
bin/repl
```

Starts a REPL and an nREPL server (the port is stored in `.nrepl-port`).

[LICENSE]: https://github.com/randomseed-io/phone-number/blob/master/LICENSE