Skip to content

Commit

Permalink
Merge pull request #9 from dominikwinter/8-add-validation
Browse files Browse the repository at this point in the history
add validation feature
  • Loading branch information
dominikwinter authored Feb 1, 2024
2 parents 4140dd5 + 1494938 commit 61df7e1
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 14 deletions.
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Swedish Personnummer Generator

A simple command-line interface (CLI) application written in Go that generates valid [Swedish "Personnummer" (personal identification numbers)](https://en.wikipedia.org/wiki/Personal_identity_number_(Sweden)).
A simple command-line interface (CLI) application written in Go that generates or validates [Swedish "Personnummer" (personal identification numbers)](https://en.wikipedia.org/wiki/Personal_identity_number_(Sweden)).

## Install

Expand All @@ -13,24 +13,35 @@ A simple command-line interface (CLI) application written in Go that generates v
go install github.com/dominikwinter/pno@latest
```
## Usage
generate a personnummer:
```bash
pno gen -h

Usage of pno gen:
-c string
Country code (default random)
Country code (default random)
-d string
Date (format: yyyymmdd) (default random)
Date (format: yyyymmdd) (default random)
-f string
Output forma (default 3):
1. yymmddccgn
2. yymmdd-ccgn
3. yyyymmddccgn
4. yyyymmdd-ccgn
Output forma (default 3):
1. yymmddccgn
2. yymmdd-ccgn
3. yyyymmddccgn
4. yyyymmdd-ccgn
-g string
Gender (m/f) (default random)
-h Help
-v Verbose
Gender (m/f) (default random)
-h Help
-v Verbose
```

validate a personnummer:
```bash
pno val -h

Usage of pno val <pno>:
-e Return exit code 1 if not valid
-h Help
-v Verbose
```
## Development
Expand Down
2 changes: 1 addition & 1 deletion cmd/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/dominikwinter/pno/internal/gen"
)

func Run(args []string) {
func Gen(args []string) {
fs := flag.NewFlagSet("pno gen", flag.ExitOnError)

helpFlag := fs.Bool("h", false, "Help")
Expand Down
49 changes: 49 additions & 0 deletions cmd/val.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cmd

import (
"flag"
"fmt"
"os"

"github.com/dominikwinter/pno/internal/val"
)

func Val(args []string) {
fs := flag.NewFlagSet("pno val <pno>", flag.ExitOnError)

helpFlag := fs.Bool("h", false, "Help")
verboseFlag := fs.Bool("v", false, "Verbose")
exitFlag := fs.Bool("e", false, "Return exit code 1 if not valid")

fs.Parse(args)

if *helpFlag || len(args) == 0 {
fs.Usage()
return
}

pno := args[len(args)-1]
valid, _, err := val.ValidatePno(pno)

if *verboseFlag {
if err != nil {
panic(err)
}
}

if *exitFlag {
if valid {
os.Exit(0)
} else {
os.Exit(1)
}
return
} else {
if valid {
fmt.Println("valid")
} else {
fmt.Println("invalid")
}
return
}
}
67 changes: 67 additions & 0 deletions internal/val/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package val

import (
"errors"
"fmt"
"regexp"
"strconv"
"time"

"github.com/dominikwinter/pno/internal/gen"
)

var re = regexp.MustCompile(`(\d{2,4})(\d{2})(\d{2})-?(\d{2})(\d)(\d)`)

func ValidatePno(pno string) (bool, string, error) {
results := re.FindAllStringSubmatch(pno, -1)

if len(results) != 1 {
return false, "", errors.New("invalid format")
}

matches := results[0]

if len(matches) != 7 {
return false, "", errors.New("invalid format")
}

year, err := fillYear(matches[1])

if err != nil {
return false, "", err
}

date := year + matches[2] + matches[3]
country := matches[4]
gender := matches[5]
checksum := matches[6]

genChecksum, err := gen.GetChecksum(date, country, gender)

if err != nil {
return false, "", err
}

return genChecksum == checksum, genChecksum, nil
}

func fillYear(year string) (string, error) {
if len(year) == 2 {
currentYear := time.Now().Year()
year, err := strconv.Atoi(year)

if err != nil {
return "", fmt.Errorf("invalid year input: %w", err)
}

century := currentYear / 100

if year < (currentYear % 100) {
century++
}

return strconv.Itoa(century*100 + year), nil
}

return year, nil
}
87 changes: 87 additions & 0 deletions internal/val/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package val

import (
"testing"
)

func Test_ValidatePno(t *testing.T) {
t.Run("valid", func(t *testing.T) {
tests := [...]string{
"4112098357",
"3311305498",
"7112050112",
"7412188927",
"3812145997",
"6701203199",
"8004113877",
"9806275906",
"2802107041",
"0007060833",
"991227-0171",
"720813-5637",
"370123-4878",
"350717-7297",
"871010-5225",
"651214-3329",
"041008-1400",
"750930-7117",
"821010-9701",
"200303178578",
"193902179021",
"198801266217",
"197511269453",
"200505206169",
"199304288872",
"195012150594",
"200003170933",
"196908054858",
"199702051559",
"200010201317",
"196106122234",
"19410308-0778",
"19341028-8843",
"19840928-9744",
"19340815-3322",
"19921220-7667",
"20000104-6416",
"19721218-8754",
"20020226-3695",
}

for _, pno := range tests {
valid, checksum, err := ValidatePno(pno)
if err != nil {
t.Error(err)
}

if !valid {
t.Errorf("'%s' should be a valid PNO. checksum is '%s'", pno, checksum)
}
}
})

t.Run("invalid", func(t *testing.T) {
tests := [...]string{
"198003257629",
"19800325762",
"1980032576",
"198003257",
"19800325",
"1980032",
"198003",
"19800",
"1980",
"198",
"19",
"1",
}

for _, pno := range tests {
valid, checksum, _ := ValidatePno(pno)

if valid {
t.Errorf("'%s' should be a invalid PNO. checksum is '%s'", pno, checksum)
}
}
})
}
9 changes: 7 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (
)

func main() {
const usage = `Usage of pno <command> [<args>]:
const usage = `Usage of pno <command> [<args>]
commands:
gen Generate a personal number
val Validate a personal number
args:
-h Help`
Expand All @@ -26,7 +27,11 @@ func main() {

switch command {
case "gen":
cmd.Run(args)
cmd.Gen(args)

case "val":
cmd.Val(args)

default:
fmt.Fprintf(os.Stderr, "Unrecognized command %s.\n\n%s\n", command, usage)
os.Exit(1)
Expand Down

0 comments on commit 61df7e1

Please sign in to comment.