13 Commits

Author SHA1 Message Date
a54d4d6d64 remove pointers for immutability 2022-01-12 12:30:11 -06:00
9fb0df401e Update test to compare to the correct result 2022-01-11 13:31:36 -06:00
223acbf427 update main test to match any ipv4 2022-01-11 13:30:06 -06:00
1fd5cebabb Change error handling and update all tests 2022-01-10 15:15:18 -06:00
32dbc95581 Fix VSCode task asking for output checking 2022-01-10 15:14:54 -06:00
a7d010334c Fix non pointer reference
I broke this not realizing I needed to use a pointer reference.
2022-01-10 15:14:32 -06:00
ec14e26e83 add main test 2022-01-07 21:47:28 +00:00
b6670cc921 fix pointers 2022-01-07 21:47:18 +00:00
9c58c2af2e fix ip api test 2022-01-07 21:46:57 +00:00
711bed47f7 update gitignore 2022-01-07 21:46:29 +00:00
6efd033bb9 add vscode test task 2022-01-07 21:45:17 +00:00
cee1c30c63 Change default to return self Public IP 2021-11-16 22:56:02 -06:00
cc439eb7d4 yet another refactor 2021-11-16 20:53:58 -06:00
8 changed files with 188 additions and 89 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,2 @@
bin/* bin/
lookupip

8
.vscode/tasks.json vendored
View File

@@ -14,7 +14,13 @@
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"clear": true "clear": true
}, }
},
{
"label": "Go Test All",
"type": "shell",
"command": "go test ./...",
"problemMatcher": []
} }
] ]
} }

57
main.go
View File

@@ -2,54 +2,25 @@ package main
import ( import (
"flag" "flag"
"fmt"
"strings"
"github.com/ryanehamil/lookupip/src/ipapi" "github.com/ryanehamil/lookupip/src/ipapi"
"github.com/ryanehamil/lookupip/src/utils"
) )
var verbose bool = false
var detail bool
var ip string
var properties []string
func main() { func main() {
parseFlags() ip := flag.String("ip", "", "IP address to lookup")
properties := flag.String("p", "", "Properties to retrieve")
if verbose { detail := flag.Bool("d", false, "Show Detail")
fmt.Println("Verbose mode on")
}
data, err := ipapi.Lookup(ip, properties)
if err != nil {
fmt.Println(err)
return
}
result := ipapi.GetProperties(data, properties, detail)
fmt.Println(result)
}
func parseFlags() {
_ip := flag.String("ip", "", "IP address to lookup")
_props := flag.String("p", "Country", "Properties to retrieve")
flag.BoolVar(&detail, "d", false, "Show Detail")
_verbose := flag.Bool("v", false, "Verbose output")
flag.Parse() flag.Parse()
_loose := flag.Args()
if *_verbose { // Use the IP-API to lookup the IP address
verbose = true data, err := ipapi.Lookup(*ip)
} utils.HandleError(err)
if *_ip == "" {
if len(_loose) == 0 { // Format the data to a string
} else { result := ipapi.GetProperties(data, *properties, *detail)
ip = _loose[0]
} // Print result with PrintOut
} else { utils.PrintOut(result)
ip = *_ip utils.Exit(0)
}
if _props != nil {
properties = strings.Split(*_props, ",")
}
} }

38
main_test.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"testing"
"github.com/ryanehamil/lookupip/src/ipapi"
"github.com/ryanehamil/lookupip/src/utils"
)
// TestBuildURL
// Tests that the buildURL function returns a correct URL
func TestLookup(t *testing.T) {
var tests = []struct {
input string
want string
explain string
}{
{"", "Any IPV4", "Lookup my own IP"},
{"8.8.8.8", "United States", "Lookup Google's DNS"},
}
for _, test := range tests {
ip := test.input
// Use the IP-API to lookup anything
data, _ := ipapi.Lookup(ip)
got := ipapi.GetProperties(data, "", false)
if test.want == "Any IPV4" {
if !utils.CheckValidIP(data.Query) {
t.Errorf("%q. error-buildURL(%q) = %v, want %v", test.explain, test.input, got, test.want)
}
} else if got != test.want {
t.Errorf("%q. lookup(%q) = %v, want %v", test.explain, test.input, got, test.want)
}
}
}

View File

@@ -3,8 +3,10 @@ package ipapi
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"net/http" "net/http"
"reflect" "reflect"
"strings"
"github.com/ryanehamil/lookupip/src/utils" "github.com/ryanehamil/lookupip/src/utils"
) )
@@ -36,51 +38,70 @@ type IPAPI struct {
Message string `json:"message"` Message string `json:"message"`
} }
// Build URL to query IP-API // Add the .String() method to IPAPI
// https://ip-api.com/docs/api:json func (i *IPAPI) String() string {
func buildURL(ip string) string { return i.Query
return "http://ip-api.com/json/" + ip
} }
func Lookup(ip string, properties []string) (*IPAPI, error) { // Build URL to query IP-API
var data *IPAPI // https://ip-api.com/docs/api:json
func buildURL(ip string) (string, error) {
url := "http://ip-api.com/json"
if ip != "" {
ip = strings.TrimSpace(ip)
if !utils.CheckValidIP(ip) {
var err = errors.New("invalid IP address, failed CheckValidIP")
return url, err
}
url += fmt.Sprintf("/%s", ip)
}
return url, nil
}
if !utils.CheckValidIP(ip) { // Get IP-API data about IP
return data, errors.New("please enter a valid IP address") //
// https://ip-api.com/docs/api:json
func Lookup(ip string) (data IPAPI, err error) {
url, error := buildURL(ip)
if error != nil {
return data, error
} }
url := buildURL(ip)
resp, err := http.Get(url) resp, err := http.Get(url)
if error != nil {
if err != nil { return data, error
// DebugOut(Error, err.Error())
return data, err
} }
defer resp.Body.Close() defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&data) err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil { if error != nil {
// DebugOut(Error, err.Error()) return data, error
return data, err
} }
if data.Status == "fail" { if data.Status == "fail" {
// DebugOut(Error, data.Message) utils.PrintOut("IP-API returned an error: " + data.Message)
return data, errors.New(data.Message) utils.Exit(1)
} }
return data, nil return data, nil
} }
func GetProperties(data *IPAPI, properties []string, detail bool) string { func GetProperties(data IPAPI, properties_string string, detail bool) (output string) {
var result string if properties_string == "" {
var output string = "" properties_string = "Country"
}
result := ""
properties := strings.Split(properties_string, ",")
for _, property := range properties { for _, property := range properties {
datafield := reflect.Indirect(reflect.ValueOf(data)).FieldByName(property).String() datafield := reflect.Indirect(reflect.ValueOf(data)).FieldByName(property).String()
if datafield != "" { if datafield != "" {
result = datafield result = datafield
} else { } else {
result = "Not found" result = ""
} }
if detail { if detail {
output += property + ": " + result + "\n" output += property + ": " + result + "\n"
} else { } else {

View File

@@ -2,6 +2,8 @@ package ipapi
import ( import (
"testing" "testing"
"github.com/ryanehamil/lookupip/src/utils"
) )
// TestBuildURL // TestBuildURL
@@ -12,13 +14,20 @@ func TestBuildURL(t *testing.T) {
want string want string
explain string explain string
}{ }{
{"127.0.0.1", "http://ip-api.com/json/127.0.0.1", "Valid Return"}, {"127.0.0.1", "http://ip-api.com/json/127.0.0.1", "Valid IP"},
{" 127.0.0.1 ", "http://ip-api.com/json/127.0.0.1", "Dirty IP"},
{"127.foo.bar.1", "invalid IP address, failed CheckValidIP", "Unsantized input"},
} }
for _, test := range tests { for _, test := range tests {
got := buildURL(test.ip) got, err := buildURL(test.ip)
if got != test.want {
t.Errorf("CheckValidIP(%q) = %v, want %v", test.ip, got, test.want) if err != nil {
if err.Error() != test.want {
t.Errorf("%q. error-buildURL(%q) = %v, want %v", test.explain, test.ip, got, test.want)
}
} else if got != test.want {
t.Errorf("%q. buildURL(%q) = %v, want %v", test.explain, test.ip, got, test.want)
} }
} }
} }
@@ -31,17 +40,48 @@ func TestLookup(t *testing.T) {
want string want string
explain string explain string
}{ }{
{"8.8.8.8", "United States", "Valid Return"}, {"8.8.8.8", "United States", "Google Public DNS"},
{"199.211.133.90", "United States", "Naval Oceanography"},
{"116.202.3.251", "Germany", "JAM Software Germany"},
{"", "Any IP", "Get my IP"},
} }
for _, test := range tests { for _, test := range tests {
got, err := Lookup(test.ip, []string{"country"}) data, err := Lookup(test.ip)
if err != nil { got := data.Country
t.Errorf("CheckValidIP(%q) = %v, want %v", test.ip, err, test.want) if err != nil && err.Error() != test.want {
} t.Errorf("%q. error-buildURL(%q) = %v, want %v", test.explain, test.ip, got, test.want)
if test.want != got.Country { } else if test.want == "Any IP" {
t.Errorf("CheckValidIP(%q) = %v, want %v", test.ip, got, test.want) if !utils.CheckValidIP(data.Query) {
t.Errorf("%q. error-buildURL(%q) = %v, want %v", test.explain, test.ip, got, test.want)
}
} else if got != test.want {
t.Errorf("%q. buildURL(%q) = %v, want %v", test.explain, test.ip, got, test.want)
} }
} }
} }
func TestGetProperties(t *testing.T) {
var tests = []struct {
properties string
want string
explain string
}{
{"", "United States", "No properties requested"},
{"Country", "United States", "Properties: Country"},
{"Country,ISP", "United States,Google LLC", "Properties: Country,ISP"},
}
for _, test := range tests {
// This test relies on the lookup function
ip := "8.8.8.8"
// Use the IP-API to lookup anything
data, _ := Lookup(ip)
got := GetProperties(data, test.properties, false)
if got != test.want {
t.Errorf("%q. GetProperties(%q) = %v, want %v", test.explain, test.properties, got, test.want)
}
}
}

View File

@@ -1,19 +1,42 @@
package utils package utils
import ( import (
"fmt"
"os"
"regexp" "regexp"
) )
type Verbosity int // For printing to the console
//
const ( // Currently using fmt.Println
Info Verbosity = iota func PrintOut(msg ...interface{}) {
Warning fmt.Println(msg...)
Error }
Debug
)
// Check if a string is a valid ipv4 address
//
// Currently using regexp.MustCompile and regexp.MatchString to return boolean
func CheckValidIP(ip string) bool { func CheckValidIP(ip string) bool {
re := regexp.MustCompile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`) re := regexp.MustCompile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`)
return re.MatchString(ip) return re.MatchString(ip)
} }
// Error handler for the program
//
// Checks if error !nil and prints the error to the console
func HandleError(err error) {
if err != nil {
PrintOut(err)
Exit(1)
}
}
// Simple Exit function to handle exit codes
//
// Might need to be changed to a more robust one
func Exit(code int) {
if code != 0 {
panic("exit")
}
os.Exit(code)
}

View File

@@ -12,13 +12,13 @@ func TestCheckValidIP(t *testing.T) {
explain string explain string
}{ }{
{"127.0.0.1", true, "Valid IP"}, {"127.0.0.1", true, "Valid IP"},
{"127.O.O.1", false, "Invalid IP"}, {"127.X.X.1", false, "Invalid IP"},
} }
for _, test := range tests { for _, test := range tests {
got := CheckValidIP(test.ip) got := CheckValidIP(test.ip)
if got != test.want { if got != test.want {
t.Errorf("CheckValidIP(%q) = %v, want %v", test.ip, got, test.want) t.Errorf("%q. CheckValidIP(%q) = %v", test.explain, test.ip, got)
} }
} }
} }