From 9746ae0a6f099b29944bd55303af642d77af8e70 Mon Sep 17 00:00:00 2001
From: Sam <sam@samtherapy.net>
Date: Thu, 15 Sep 2022 11:02:43 +0000
Subject: [PATCH] feat: more API work

Reviewed-on: https://git.froth.zone/sam/awl/pulls/94
Reviewed-by: grumbulon <grumbulon@grumbulon.xyz>
---
 .drone.jsonnet         |  12 +-
 .editorconfig          |  12 ++
 .golangci.yaml         |  11 ++
 cli/cli.go             |  46 ++++----
 cli/cli_test.go        | 117 ++++++------------
 cli/dig.go             |   8 +-
 go.mod                 |  13 +-
 go.sum                 |  14 +--
 logawl/logger.go       |  20 ++++
 logawl/logging_test.go |   5 +
 main.go                |   8 +-
 main_test.go           |  22 +---
 pkg/awl-dns-git        |   2 +-
 query/QUIC_test.go     |   8 +-
 query/general_test.go  |  11 +-
 query/print.go         | 262 ++++++++++++++++++++++++++++++++++++++---
 query/print_test.go    | 111 +++++++++--------
 query/query.go         |   6 +-
 query/query_test.go    |  42 ++++---
 query/resolver.go      |   4 +-
 query/struct.go        | 262 +++++++----------------------------------
 template.mk            |   4 +-
 util/options.go        | 175 ++++++++++++++++++---------
 util/query.go          |  26 ++--
 24 files changed, 661 insertions(+), 540 deletions(-)
 create mode 100644 .editorconfig

diff --git a/.drone.jsonnet b/.drone.jsonnet
index 6ac4e64..95a30fb 100644
--- a/.drone.jsonnet
+++ b/.drone.jsonnet
@@ -8,19 +8,9 @@ local testing(version, arch) = {
     arch: arch
   },
   steps: [
-    {
-      name: "compile",
-      image: "golang:" + version,
-      commands: [
-        "make awl"
-      ],
-    },
     {
       name: "lint",
       image: "rancher/drone-golangci-lint:latest",
-      depends_on: [
-        "compile",
-      ],
     },
     {
       name: "test",
@@ -74,7 +64,7 @@ local release() = {
       name: "test",
       image: "golang",
       commands: [
-        "make test"
+        "make test-ci"
       ]
     },
     {
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..ebe51d3
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = false
+insert_final_newline = false
\ No newline at end of file
diff --git a/.golangci.yaml b/.golangci.yaml
index 9eca14e..badf518 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -32,6 +32,7 @@ linters:
     - predeclared
     - revive
     - staticcheck
+    - tagliatelle
     - whitespace
     - wrapcheck
     - wsl
@@ -70,6 +71,16 @@ linters-settings:
       - name: unexported-return
       - name: var-declaration
       - name: var-naming
+  linters-settings:
+  tagliatelle:
+    case:
+      use-field-name: false
+      rules:
+        # Any struct tag type can be used.
+        # Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
+        json: goCamel
+        yaml: goCamel
+        xml: goCamel
 
 issues:
   exclude-use-default: false
diff --git a/cli/cli.go b/cli/cli.go
index 8a593cb..f002afe 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -5,7 +5,6 @@ package cli
 import (
 	"errors"
 	"fmt"
-	"os"
 	"runtime"
 	"strconv"
 	"strings"
@@ -18,8 +17,8 @@ import (
 
 // ParseCLI parses arguments given from the CLI and passes them into an `Options`
 // struct.
-func ParseCLI(version string) (util.Options, error) {
-	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
+func ParseCLI(args []string, version string) (util.Options, error) {
+	flag.CommandLine = flag.NewFlagSet(args[0], flag.ContinueOnError)
 
 	flag.Usage = func() {
 		fmt.Println(`awl - drill, writ small
@@ -104,7 +103,7 @@ func ParseCLI(version string) (util.Options, error) {
 	flag.CommandLine.SortFlags = false
 
 	// Parse the flags
-	if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
+	if err := flag.CommandLine.Parse(args[1:]); err != nil {
 		return util.Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag: %w", err)
 	}
 
@@ -116,7 +115,6 @@ func ParseCLI(version string) (util.Options, error) {
 
 	opts := util.Options{
 		Logger:      util.InitLogger(*verbosity),
-		Port:        *port,
 		IPv4:        *ipv4,
 		IPv6:        *ipv6,
 		Short:       *short,
@@ -128,29 +126,29 @@ func ParseCLI(version string) (util.Options, error) {
 		HTTPS:       *https,
 		QUIC:        *quic,
 		Truncate:    *truncate,
-		ShowQuery:   false,
-		AA:          *aaflag,
-		AD:          *adflag,
-		TC:          *tcflag,
-		Z:           *zflag,
-		CD:          *cdflag,
-		QR:          *qrflag,
-		RD:          *rdflag,
-		RA:          *raflag,
 		Reverse:     *reverse,
-		HumanTTL:    false,
-		ShowTTL:     true,
 		JSON:        *json,
 		XML:         *xml,
 		YAML:        *yaml,
+		HeaderFlags: util.HeaderFlags{
+			AA: *aaflag,
+			AD: *adflag,
+			TC: *tcflag,
+			Z:  *zflag,
+			CD: *cdflag,
+			QR: *qrflag,
+			RD: *rdflag,
+			RA: *raflag,
+		},
 		Request: util.Request{
 			Type:    dns.StringToType[strings.ToUpper(*qType)],
 			Class:   dns.StringToClass[strings.ToUpper(*class)],
 			Name:    *query,
 			Timeout: time.Duration(*timeout * float32(time.Second)),
 			Retries: *retry,
+			Port:    *port,
 		},
-		Display: util.Displays{
+		Display: util.Display{
 			Comments:   !*noC,
 			Question:   !*noQ,
 			Opt:        !*noOpt,
@@ -158,6 +156,9 @@ func ParseCLI(version string) (util.Options, error) {
 			Authority:  !*noAuth,
 			Additional: !*noAdd,
 			Statistics: !*noStats,
+			HumanTTL:   false,
+			ShowQuery:  false,
+			TTL:        true,
 		},
 		EDNS: util.EDNS{
 			EnableEDNS: !*edns,
@@ -199,15 +200,15 @@ func ParseCLI(version string) (util.Options, error) {
 	opts.Logger.Info("Dig/Drill flags parsed")
 	opts.Logger.Debug(fmt.Sprintf("%+v", opts))
 
-	if opts.Port == 0 {
+	if opts.Request.Port == 0 {
 		if opts.TLS || opts.QUIC {
-			opts.Port = 853
+			opts.Request.Port = 853
 		} else {
-			opts.Port = 53
+			opts.Request.Port = 53
 		}
 	}
 
-	opts.Logger.Info("Port set to", opts.Port)
+	opts.Logger.Info("Port set to", opts.Request.Port)
 
 	// Set timeout to 0.5 seconds if set below 0.5
 	if opts.Request.Timeout < (time.Second / 2) {
@@ -218,6 +219,9 @@ func ParseCLI(version string) (util.Options, error) {
 		opts.Request.Retries = 0
 	}
 
+	opts.Logger.Info("Options fully populated")
+	opts.Logger.Debug(fmt.Sprintf("%+v", opts))
+
 	return opts, nil
 }
 
diff --git a/cli/cli_test.go b/cli/cli_test.go
index 0930fbe..e830c2f 100644
--- a/cli/cli_test.go
+++ b/cli/cli_test.go
@@ -3,7 +3,6 @@
 package cli_test
 
 import (
-	"os"
 	"testing"
 	"time"
 
@@ -12,156 +11,110 @@ import (
 )
 
 func TestEmpty(t *testing.T) {
-	args := os.Args
-	os.Args = []string{"awl", "-4"}
+	args := []string{"awl", "-4"}
 
-	opts, err := cli.ParseCLI("TEST")
+	opts, err := cli.ParseCLI(args, "TEST")
 
 	assert.NilError(t, err)
-	assert.Equal(t, opts.Port, 53)
+	assert.Equal(t, opts.Request.Port, 53)
 	assert.Assert(t, opts.IPv4)
-
-	os.Args = args
 }
 
 func TestTLSPort(t *testing.T) {
-	args := os.Args
-	os.Args = []string{"awl", "-T"}
+	args := []string{"awl", "-T"}
 
-	opts, err := cli.ParseCLI("TEST")
+	opts, err := cli.ParseCLI(args, "TEST")
 
 	assert.NilError(t, err)
-	assert.Equal(t, opts.Port, 853)
-
-	os.Args = args
+	assert.Equal(t, opts.Request.Port, 853)
 }
 
 func TestSubnet(t *testing.T) {
-	args := os.Args
-	os.Args = []string{"awl", "--subnet", "127.0.0.1/32"}
+	args := []string{"awl", "--subnet", "127.0.0.1/32"}
 
-	opts, err := cli.ParseCLI("TEST")
+	opts, err := cli.ParseCLI(args, "TEST")
 
 	assert.NilError(t, err)
 	assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
 
-	os.Args = args
+	args = []string{"awl", "--subnet", "0"}
 
-	os.Args = []string{"awl", "--subnet", "0"}
-
-	opts, err = cli.ParseCLI("TEST")
+	opts, err = cli.ParseCLI(args, "TEST")
 	assert.NilError(t, err)
 	assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
 
-	os.Args = args
+	args = []string{"awl", "--subnet", "::/0"}
 
-	os.Args = []string{"awl", "--subnet", "::/0"}
-
-	opts, err = cli.ParseCLI("TEST")
+	opts, err = cli.ParseCLI(args, "TEST")
 	assert.NilError(t, err)
 	assert.Equal(t, opts.EDNS.Subnet.Family, uint16(2))
 
-	os.Args = args
+	args = []string{"awl", "--subnet", "/"}
 
-	os.Args = []string{"awl", "--subnet", "/"}
-
-	opts, err = cli.ParseCLI("TEST")
+	opts, err = cli.ParseCLI(args, "TEST")
 	assert.ErrorContains(t, err, "EDNS subnet")
-
-	os.Args = args
 }
 
-func TestMBZ(t *testing.T) { //nolint: paralleltest // Race conditions
-	args := os.Args
-	os.Args = []string{"awl", "--zflag", "G"}
+func TestMBZ(t *testing.T) {
+	args := []string{"awl", "--zflag", "G"}
 
-	_, err := cli.ParseCLI("TEST")
+	_, err := cli.ParseCLI(args, "TEST")
 
 	assert.ErrorContains(t, err, "EDNS MBZ")
-
-	os.Args = args
 }
 
-func TestInvalidFlag(t *testing.T) { //nolint: paralleltest // Race conditions
-	args := os.Args
-	stdout := os.Stdout
-	stderr := os.Stderr
+func TestInvalidFlag(t *testing.T) {
+	args := []string{"awl", "--treebug"}
 
-	os.Stdout = os.NewFile(0, os.DevNull)
-	os.Stderr = os.NewFile(0, os.DevNull)
-
-	os.Args = []string{"awl", "--treebug"}
-
-	_, err := cli.ParseCLI("TEST")
+	_, err := cli.ParseCLI(args, "TEST")
 
 	assert.ErrorContains(t, err, "unknown flag")
-
-	os.Args = args
-	os.Stdout = stdout
-	os.Stderr = stderr
 }
 
-func TestInvalidDig(t *testing.T) { //nolint: paralleltest // Race conditions
-	args := os.Args
-	os.Args = []string{"awl", "+a"}
+func TestInvalidDig(t *testing.T) {
+	args := []string{"awl", "+a"}
 
-	_, err := cli.ParseCLI("TEST")
+	_, err := cli.ParseCLI(args, "TEST")
 
 	assert.ErrorContains(t, err, "digflags: invalid argument")
-
-	os.Args = args
 }
 
-func TestVersion(t *testing.T) { //nolint: paralleltest // Race conditions
-	args := os.Args
-	stdout := os.Stdout
-	stderr := os.Stderr
+func TestVersion(t *testing.T) {
+	args := []string{"awl", "--version"}
 
-	os.Args = []string{"awl", "--version"}
-
-	_, err := cli.ParseCLI("test")
+	_, err := cli.ParseCLI(args, "test")
 
 	assert.ErrorType(t, err, cli.ErrNotError)
-
-	os.Args = args
-	os.Stdout = stdout
-	os.Stderr = stderr
 }
 
-func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions
+func TestTimeout(t *testing.T) {
 	args := [][]string{
 		{"awl", "+timeout=0"},
 		{"awl", "--timeout", "0"},
 	}
 	for _, test := range args {
-		args := os.Args
-		os.Args = test
+		test := test
 
-		opt, err := cli.ParseCLI("TEST")
+		opt, err := cli.ParseCLI(test, "TEST")
 
 		assert.NilError(t, err)
 		assert.Equal(t, opt.Request.Timeout, time.Second/2)
-
-		os.Args = args
 	}
 }
 
-func TestRetries(t *testing.T) { //nolint: paralleltest // Race conditions
+func TestRetries(t *testing.T) {
 	args := [][]string{
 		{"awl", "+retry=-2"},
 		{"awl", "+tries=-2"},
 		{"awl", "--retries", "-2"},
 	}
 	for _, test := range args {
-		args := os.Args
-		os.Args = test
+		test := test
 
-		opt, err := cli.ParseCLI("TEST")
+		opt, err := cli.ParseCLI(test, "TEST")
 
 		assert.NilError(t, err)
 		assert.Equal(t, opt.Request.Retries, 0)
-
-		os.Args = args
 	}
 }
 
@@ -175,10 +128,8 @@ func FuzzFlags(f *testing.F) {
 	f.Fuzz(func(t *testing.T, orig string) {
 		// Get rid of outputs
 
-		args := os.Args
-		os.Args = []string{"awl", orig}
+		args := []string{"awl", orig}
 		//nolint:errcheck,gosec // Only make sure the program does not crash
-		cli.ParseCLI("TEST")
-		os.Args = args
+		cli.ParseCLI(args, "TEST")
 	})
 }
diff --git a/cli/dig.go b/cli/dig.go
index 6d75b6c..00ee8a6 100644
--- a/cli/dig.go
+++ b/cli/dig.go
@@ -45,13 +45,13 @@ func ParseDig(arg string, opts *util.Options) error {
 	// End DNS query flags
 
 	case "qr":
-		opts.ShowQuery = isNo
+		opts.Display.ShowQuery = isNo
 	case "ttlunits":
-		opts.HumanTTL = isNo
+		opts.Display.HumanTTL = isNo
 	case "ttl", "ttlid":
-		opts.ShowTTL = isNo
+		opts.Display.TTL = isNo
 	case "class":
-		opts.ShowClass = isNo
+		opts.Display.ShowClass = isNo
 
 	// EDNS queries
 	case "dnssec":
diff --git a/go.mod b/go.mod
index fa831c7..9b6a2f8 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
 	github.com/miekg/dns v1.1.50
 	github.com/stefansundin/go-zflag v1.1.1
 	golang.org/x/net v0.0.0-20220909164309-bea034e7d591
+	golang.org/x/sys v0.0.0-20220913175220-63ea55921009
 	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools/v3 v3.3.0
 )
@@ -18,22 +19,18 @@ require (
 	github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
 	github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
 	github.com/ameshkov/dnsstamps v1.0.3 // indirect
+	github.com/fsnotify/fsnotify v1.5.4 // indirect
+	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/golang/mock v1.6.0 // indirect
 	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/kr/pretty v0.3.0 // indirect
-	github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
-	golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 // indirect
-)
-
-require (
-	github.com/fsnotify/fsnotify v1.5.4 // indirect
-	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
+	github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo v1.16.5 // indirect
 	golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
+	golang.org/x/exp v0.0.0-20220914170420-dc92f8653013 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
-	golang.org/x/sys v0.0.0-20220913175220-63ea55921009
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/tools v0.1.12 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
diff --git a/go.sum b/go.sum
index 4863d95..5defd9f 100644
--- a/go.sum
+++ b/go.sum
@@ -79,10 +79,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
-golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
-golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
-golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/exp v0.0.0-20220914170420-dc92f8653013 h1:ZjglnWxEUdPyXl4o/j4T89SRCI+4X6NW6185PNLEOF4=
+golang.org/x/exp v0.0.0-20220914170420-dc92f8653013/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
@@ -94,8 +92,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220907135653-1e95f45603a7 h1:1WGATo9HAhkWMbfyuVU0tEFP88OIkUvwaHFveQPvzCQ=
-golang.org/x/net v0.0.0-20220907135653-1e95f45603a7/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
 golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -119,12 +115,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U=
-golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
-golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw=
 golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
diff --git a/logawl/logger.go b/logawl/logger.go
index e6aebfd..b0993ea 100644
--- a/logawl/logger.go
+++ b/logawl/logger.go
@@ -139,17 +139,37 @@ func (l *Logger) Debug(v ...any) {
 	l.Println(DebugLevel, v...)
 }
 
+// Debugf calls print after formatting the string with Debug level.
+func (l *Logger) Debugf(format string, v ...any) {
+	l.Println(ErrLevel, fmt.Sprintf(format, v...))
+}
+
 // Info calls print directly with Info level.
 func (l *Logger) Info(v ...any) {
 	l.Println(InfoLevel, v...)
 }
 
+// Infof calls print after formatting the string with Info level.
+func (l *Logger) Infof(format string, v ...any) {
+	l.Println(ErrLevel, fmt.Sprintf(format, v...))
+}
+
 // Warn calls print directly with Warn level.
 func (l *Logger) Warn(v ...any) {
 	l.Println(WarnLevel, v...)
 }
 
+// Warnf calls print after formatting the string with Warn level.
+func (l *Logger) Warnf(format string, v ...any) {
+	l.Println(WarnLevel, fmt.Sprintf(format, v...))
+}
+
 // Error calls print directly with Error level.
 func (l *Logger) Error(v ...any) {
 	l.Println(ErrLevel, v...)
 }
+
+// Errorf calls print after formatting the string with Error level.
+func (l *Logger) Errorf(format string, v ...any) {
+	l.Println(ErrLevel, fmt.Sprintf(format, v...))
+}
diff --git a/logawl/logging_test.go b/logawl/logging_test.go
index 1b63b01..24f001c 100644
--- a/logawl/logging_test.go
+++ b/logawl/logging_test.go
@@ -52,6 +52,7 @@ func TestLogger(t *testing.T) {
 		case 0:
 			fn := func() {
 				logger.Error("Test", "E")
+				logger.Errorf("%s", "Test")
 			}
 
 			var buffer bytes.Buffer
@@ -62,6 +63,7 @@ func TestLogger(t *testing.T) {
 		case 1:
 			fn := func() {
 				logger.Warn("Test")
+				logger.Warnf("%s", "Test")
 			}
 
 			var buffer bytes.Buffer
@@ -72,6 +74,7 @@ func TestLogger(t *testing.T) {
 		case 2:
 			fn := func() {
 				logger.Info("Test")
+				logger.Infof("%s", "Test")
 			}
 
 			var buffer bytes.Buffer
@@ -83,6 +86,8 @@ func TestLogger(t *testing.T) {
 			fn := func() {
 				logger.Debug("Test")
 				logger.Debug("Test 2")
+				logger.Debugf("%s", "Test")
+				logger.Debugf("%s %d", "Test", 2)
 			}
 
 			var buffer bytes.Buffer
diff --git a/main.go b/main.go
index 641deac..7771998 100644
--- a/main.go
+++ b/main.go
@@ -17,7 +17,7 @@ import (
 var version = "DEV"
 
 func main() {
-	if opts, code, err := run(); err != nil {
+	if opts, code, err := run(os.Args); err != nil {
 		// TODO: Make not ew
 		if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") {
 			os.Exit(0)
@@ -28,8 +28,8 @@ func main() {
 	}
 }
 
-func run() (opts util.Options, code int, err error) {
-	opts, err = cli.ParseCLI(version)
+func run(args []string) (opts util.Options, code int, err error) {
+	opts, err = cli.ParseCLI(args, version)
 	if err != nil {
 		return opts, 1, fmt.Errorf("parse: %w", err)
 	}
@@ -53,7 +53,7 @@ func run() (opts util.Options, code int, err error) {
 
 	var str string
 	if opts.JSON || opts.XML || opts.YAML {
-		str, err = query.PrintSpecial(resp.DNS, opts)
+		str, err = query.PrintSpecial(resp, opts)
 		if err != nil {
 			return opts, 10, fmt.Errorf("format print: %w", err)
 		}
diff --git a/main_test.go b/main_test.go
index eb42484..57186c9 100644
--- a/main_test.go
+++ b/main_test.go
@@ -14,33 +14,23 @@ func TestMain(t *testing.T) { //nolint: paralleltest // Race conditions
 	os.Stdout = os.NewFile(0, os.DevNull)
 	os.Stderr = os.NewFile(0, os.DevNull)
 
-	old := os.Args
+	args := []string{"awl", "+yaml", "@1.1.1.1"}
 
-	os.Args = []string{"awl", "+yaml", "@1.1.1.1"}
-
-	_, code, err := run()
+	_, code, err := run(args)
 	assert.NilError(t, err)
 	assert.Equal(t, code, 0)
 
-	os.Args = []string{"awl", "+short", "@1.1.1.1"}
+	args = []string{"awl", "+short", "@1.1.1.1"}
 
-	_, code, err = run()
+	_, code, err = run(args)
 	assert.NilError(t, err)
 	assert.Equal(t, code, 0)
-
-	os.Args = old
 }
 
 func TestHelp(t *testing.T) {
-	old := os.Args
-	os.Stdout = os.NewFile(0, os.DevNull)
-	os.Stderr = os.NewFile(0, os.DevNull)
+	args := []string{"awl", "-h"}
 
-	os.Args = []string{"awl", "-h"}
-
-	_, code, err := run()
+	_, code, err := run(args)
 	assert.ErrorIs(t, err, zflag.ErrHelp)
 	assert.Equal(t, code, 1)
-
-	os.Args = old
 }
diff --git a/pkg/awl-dns-git b/pkg/awl-dns-git
index c8686b1..d876d6d 160000
--- a/pkg/awl-dns-git
+++ b/pkg/awl-dns-git
@@ -1 +1 @@
-Subproject commit c8686b1e149b6c42db019c77caf36475aafadd03
+Subproject commit d876d6de34a78298ed041f575662015fb7eccdb5
diff --git a/query/QUIC_test.go b/query/QUIC_test.go
index 6158254..6d83199 100644
--- a/query/QUIC_test.go
+++ b/query/QUIC_test.go
@@ -22,8 +22,7 @@ func TestQuic(t *testing.T) {
 	opts := util.Options{
 		QUIC:    true,
 		Logger:  util.InitLogger(0),
-		Port:    853,
-		Request: util.Request{Server: "dns.adguard.com"},
+		Request: util.Request{Server: "dns.adguard.com", Port: 853},
 	}
 	testCase := util.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
 	testCase2 := util.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
@@ -54,7 +53,7 @@ func TestQuic(t *testing.T) {
 			resolver, err := query.LoadResolver(opts)
 			assert.NilError(t, err)
 
-			testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Port))
+			testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Request.Port))
 
 			// if the domain is not canonical, make it canonical
 			if !strings.HasSuffix(testCase2.Name, ".") {
@@ -78,8 +77,7 @@ func TestInvalidQuic(t *testing.T) {
 	opts := util.Options{
 		QUIC:    true,
 		Logger:  util.InitLogger(0),
-		Port:    853,
-		Request: util.Request{Server: "example.com", Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
+		Request: util.Request{Server: "example.com", Port: 853, Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
 	}
 	resolver, err := query.LoadResolver(opts)
 	assert.NilError(t, err)
diff --git a/query/general_test.go b/query/general_test.go
index 312cf60..fe06c12 100644
--- a/query/general_test.go
+++ b/query/general_test.go
@@ -17,9 +17,9 @@ func TestResolve(t *testing.T) {
 
 	opts := util.Options{
 		Logger: util.InitLogger(0),
-		Port:   53,
 		Request: util.Request{
 			Server:  "8.8.4.1",
+			Port:    53,
 			Type:    dns.TypeA,
 			Name:    "example.com.",
 			Timeout: time.Second / 2,
@@ -42,9 +42,9 @@ func TestTruncate(t *testing.T) {
 	opts := util.Options{
 		Logger: util.InitLogger(0),
 		IPv4:   true,
-		Port:   5301,
 		Request: util.Request{
 			Server: "madns.binarystar.systems",
+			Port:   5301,
 			Type:   dns.TypeTXT,
 			Name:   "limit.txt.example.",
 		},
@@ -70,9 +70,10 @@ func TestResolveAgain(t *testing.T) {
 			util.Options{
 				Logger: util.InitLogger(0),
 				TCP:    true,
-				Port:   53,
+
 				Request: util.Request{
 					Server: "8.8.4.4",
+					Port:   53,
 					Type:   dns.TypeA,
 					Name:   "example.com.",
 				},
@@ -81,9 +82,9 @@ func TestResolveAgain(t *testing.T) {
 		{
 			util.Options{
 				Logger: util.InitLogger(0),
-				Port:   53,
 				Request: util.Request{
 					Server: "8.8.4.4",
+					Port:   53,
 					Type:   dns.TypeAAAA,
 					Name:   "example.com.",
 				},
@@ -93,9 +94,9 @@ func TestResolveAgain(t *testing.T) {
 			util.Options{
 				Logger: util.InitLogger(0),
 				TLS:    true,
-				Port:   853,
 				Request: util.Request{
 					Server: "dns.google",
+					Port:   853,
 					Type:   dns.TypeAAAA,
 					Name:   "example.com.",
 				},
diff --git a/query/print.go b/query/print.go
index 5eaa6bf..d3a79c3 100644
--- a/query/print.go
+++ b/query/print.go
@@ -18,10 +18,210 @@ import (
 	"gopkg.in/yaml.v3"
 )
 
+// ToString turns the response into something that looks a lot like dig
+//
+// Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900
+func ToString(res util.Response, opts util.Options) (string, error) {
+	if res.DNS == nil {
+		return "<nil> MsgHdr", errNoMessage
+	}
+
+	var (
+		s   string
+		opt *dns.OPT
+	)
+
+	if !opts.Short {
+		if opts.Display.Comments {
+			s += res.DNS.MsgHdr.String() + " "
+			s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", "
+			s += "ANSWER: " + strconv.Itoa(len(res.DNS.Answer)) + ", "
+			s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", "
+			s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n"
+			opt = res.DNS.IsEdns0()
+
+			if opt != nil && opts.Display.Opt {
+				// OPT PSEUDOSECTION
+				s += opt.String() + "\n"
+			}
+		}
+
+		if opts.Display.Question {
+			if len(res.DNS.Question) > 0 {
+				if opts.Display.Comments {
+					s += "\n;; QUESTION SECTION:\n"
+				}
+
+				for _, r := range res.DNS.Question {
+					str, err := stringParse(r.String(), false, opts)
+					if err != nil {
+						return "", fmt.Errorf("%w", err)
+					}
+
+					s += str + "\n"
+				}
+			}
+		}
+
+		if opts.Display.Answer {
+			if len(res.DNS.Answer) > 0 {
+				if opts.Display.Comments {
+					s += "\n;; ANSWER SECTION:\n"
+				}
+
+				for _, r := range res.DNS.Answer {
+					if r != nil {
+						str, err := stringParse(r.String(), true, opts)
+						if err != nil {
+							return "", fmt.Errorf("%w", err)
+						}
+
+						s += str + "\n"
+					}
+				}
+			}
+		}
+
+		if opts.Display.Authority {
+			if len(res.DNS.Ns) > 0 {
+				if opts.Display.Comments {
+					s += "\n;; AUTHORITY SECTION:\n"
+				}
+
+				for _, r := range res.DNS.Ns {
+					if r != nil {
+						str, err := stringParse(r.String(), true, opts)
+						if err != nil {
+							return "", fmt.Errorf("%w", err)
+						}
+
+						s += str + "\n"
+					}
+				}
+			}
+		}
+
+		if opts.Display.Additional {
+			if len(res.DNS.Extra) > 0 && (opt == nil || len(res.DNS.Extra) > 1) {
+				if opts.Display.Comments {
+					s += "\n;; ADDITIONAL SECTION:\n"
+				}
+
+				for _, r := range res.DNS.Extra {
+					if r != nil && r.Header().Rrtype != dns.TypeOPT {
+						str, err := stringParse(r.String(), true, opts)
+						if err != nil {
+							return "", fmt.Errorf("%w", err)
+						}
+
+						s += str + "\n"
+					}
+				}
+			}
+		}
+
+		if opts.Display.Statistics {
+			s += "\n;; Query time: " + res.RTT.String()
+			s += "\n;; SERVER: " + opts.Request.Server + serverExtra(opts)
+			s += "\n;; WHEN: " + time.Now().Format(time.RFC1123Z)
+			s += "\n;; MSG SIZE  rcvd: " + strconv.Itoa(res.DNS.Len()) + "\n"
+		}
+	} else {
+		// Print just the responses, nothing else
+		for i, resp := range res.DNS.Answer {
+			temp := strings.Split(resp.String(), "\t")
+			s += temp[len(temp)-1]
+
+			if opts.Identify {
+				s += " from server " + opts.Request.Server + " in " + res.RTT.String()
+			}
+
+			// Don't print newline on last line
+			if i != len(res.DNS.Answer)-1 {
+				s += "\n"
+			}
+		}
+	}
+
+	return s, nil
+}
+
+func serverExtra(opts util.Options) string {
+	// Add extra information to server string
+	var extra string
+
+	switch {
+	case opts.TCP:
+		extra = ":" + strconv.Itoa(opts.Request.Port) + " (TCP)"
+	case opts.TLS:
+		extra = ":" + strconv.Itoa(opts.Request.Port) + " (TLS)"
+	case opts.HTTPS, opts.DNSCrypt:
+		extra = ""
+	case opts.QUIC:
+		extra = ":" + strconv.Itoa(opts.Request.Port) + " (QUIC)"
+	default:
+		extra = ":" + strconv.Itoa(opts.Request.Port) + " (UDP)"
+	}
+
+	return extra
+}
+
+// stringParse edits the raw responses to user requests.
+func stringParse(str string, isAns bool, opts util.Options) (string, error) {
+	split := strings.Split(str, "\t")
+
+	// Make edits if so requested
+
+	// TODO: make less ew?
+	// This exists because the question section should be left alone EXCEPT for punycode.
+
+	if isAns {
+		if !opts.Display.TTL {
+			// Remove from existence
+			split = append(split[:1], split[2:]...)
+		}
+
+		if !opts.Display.ShowClass {
+			// Position depends on if the TTL is there or not.
+			if opts.Display.TTL {
+				split = append(split[:2], split[3:]...)
+			} else {
+				split = append(split[:1], split[2:]...)
+			}
+		}
+
+		if opts.Display.TTL && opts.Display.HumanTTL {
+			ttl, _ := strconv.Atoi(split[1])
+			split[1] = (time.Duration(ttl) * time.Second).String()
+		}
+	}
+
+	if opts.Display.UcodeTranslate {
+		var (
+			err  error
+			semi string
+		)
+
+		if strings.HasPrefix(split[0], ";") {
+			split[0] = strings.TrimPrefix(split[0], ";")
+			semi = ";"
+		}
+
+		split[0], err = idna.ToUnicode(split[0])
+		if err != nil {
+			return "", fmt.Errorf("punycode: %w", err)
+		}
+
+		split[0] = semi + split[0]
+	}
+
+	return strings.Join(split, "\t"), nil
+}
+
 // PrintSpecial is for printing as JSON, XML or YAML.
 // As of now JSON and XML use the stdlib version.
-func PrintSpecial(msg *dns.Msg, opts util.Options) (string, error) {
-	formatted, err := MakePrintable(msg, opts)
+func PrintSpecial(res util.Response, opts util.Options) (string, error) {
+	formatted, err := MakePrintable(res, opts)
 	if err != nil {
 		return "", err
 	}
@@ -52,11 +252,34 @@ func PrintSpecial(msg *dns.Msg, opts util.Options) (string, error) {
 
 // MakePrintable takes a DNS message and makes it nicer to be printed as JSON,YAML,
 // and XML. Little is changed beyond naming.
-func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
-	var err error
-
+func MakePrintable(res util.Response, opts util.Options) (*Message, error) {
+	var (
+		err error
+		msg = res.DNS
+	)
+	// The things I do for compatibility
 	ret := Message{
-		Header: msg.MsgHdr,
+		Header: Header{
+			ID:                 msg.Id,
+			Response:           msg.Response,
+			Opcode:             dns.OpcodeToString[msg.Opcode],
+			Authoritative:      msg.Authoritative,
+			Truncated:          msg.Truncated,
+			RecursionDesired:   msg.RecursionDesired,
+			RecursionAvailable: msg.RecursionAvailable,
+			Zero:               msg.Zero,
+			AuthenticatedData:  msg.AuthenticatedData,
+			CheckingDisabled:   msg.CheckingDisabled,
+			Status:             dns.RcodeToString[msg.Rcode],
+		},
+	}
+
+	opt := msg.IsEdns0()
+	if opt != nil && opts.Display.Opt {
+		ret.Opt, err = parseOpt(*opt)
+		if err != nil {
+			return nil, fmt.Errorf("edns print: %w", err)
+		}
 	}
 
 	for _, question := range msg.Question {
@@ -85,8 +308,8 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
 			name string
 		)
 
-		if opts.ShowTTL {
-			if opts.HumanTTL {
+		if opts.Display.TTL {
+			if opts.Display.HumanTTL {
 				ttl = (time.Duration(answer.Header().Ttl) * time.Second).String()
 			} else {
 				ttl = strconv.Itoa(int(answer.Header().Ttl))
@@ -122,8 +345,8 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
 			name string
 		)
 
-		if opts.ShowTTL {
-			if opts.HumanTTL {
+		if opts.Display.TTL {
+			if opts.Display.HumanTTL {
 				ttl = (time.Duration(ns.Header().Ttl) * time.Second).String()
 			} else {
 				ttl = strconv.Itoa(int(ns.Header().Ttl))
@@ -139,7 +362,7 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
 			name = ns.Header().Name
 		}
 
-		ret.Ns = append(ret.Ns, Answer{
+		ret.Authority = append(ret.Authority, Answer{
 			RRHeader: RRHeader{
 				Name:     name,
 				Type:     dns.TypeToString[ns.Header().Rrtype],
@@ -162,8 +385,8 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
 				name string
 			)
 
-			if opts.ShowTTL {
-				if opts.HumanTTL {
+			if opts.Display.TTL {
+				if opts.Display.HumanTTL {
 					ttl = (time.Duration(additional.Header().Ttl) * time.Second).String()
 				} else {
 					ttl = strconv.Itoa(int(additional.Header().Ttl))
@@ -192,12 +415,15 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
 		}
 	}
 
-	opt := msg.IsEdns0()
-	if opt != nil && opts.Display.Opt {
-		ret.Opt, err = parseOpt(*opt)
-		if err != nil {
-			return nil, fmt.Errorf("edns print: %w", err)
+	if opts.Display.Statistics {
+		ret.Statistics = Statistics{
+			RTT:     res.RTT.String(),
+			Server:  opts.Request.Server + serverExtra(opts),
+			When:    time.Now().Format(time.RFC1123Z),
+			MsgSize: res.DNS.Len(),
 		}
+	} else {
+		ret.Statistics = Statistics{}
 	}
 
 	return &ret, nil
diff --git a/query/print_test.go b/query/print_test.go
index 8dce5d1..3f88e3c 100644
--- a/query/print_test.go
+++ b/query/print_test.go
@@ -16,15 +16,16 @@ func TestRealPrint(t *testing.T) {
 
 	opts := []util.Options{
 		{
-			Logger:    util.InitLogger(0),
-			Port:      53,
-			TCP:       true,
-			ShowQuery: true,
-			RD:        true,
-			ShowTTL:   true,
-			HumanTTL:  true,
-			JSON:      true,
-			Display: util.Displays{
+			Logger: util.InitLogger(0),
+
+			TCP: true,
+
+			HeaderFlags: util.HeaderFlags{
+				RD: true,
+			},
+
+			JSON: true,
+			Display: util.Display{
 				Comments:       true,
 				Question:       true,
 				Answer:         true,
@@ -32,9 +33,13 @@ func TestRealPrint(t *testing.T) {
 				Additional:     true,
 				Statistics:     true,
 				UcodeTranslate: true,
+				TTL:            true,
+				HumanTTL:       true,
+				ShowQuery:      true,
 			},
 			Request: util.Request{
 				Server: "a.gtld-servers.net",
+				Port:   53,
 				Type:   dns.StringToType["NS"],
 				Class:  1,
 				Name:   "google.com.",
@@ -44,17 +49,18 @@ func TestRealPrint(t *testing.T) {
 			},
 		},
 		{
-			Logger:    util.InitLogger(0),
-			Port:      53,
-			TCP:       true,
-			ShowQuery: true,
-			RD:        true,
+			Logger: util.InitLogger(0),
+
+			TCP: true,
+			HeaderFlags: util.HeaderFlags{
+				RD: true,
+			},
 			Verbosity: 0,
-			ShowTTL:   true,
-			Short:     true,
-			Identify:  true,
-			YAML:      false,
-			Display: util.Displays{
+
+			Short:    true,
+			Identify: true,
+			YAML:     false,
+			Display: util.Display{
 				Comments:       true,
 				Question:       true,
 				Answer:         true,
@@ -62,9 +68,12 @@ func TestRealPrint(t *testing.T) {
 				Additional:     true,
 				Statistics:     true,
 				UcodeTranslate: true,
+				TTL:            true,
+				ShowQuery:      true,
 			},
 			Request: util.Request{
 				Server:  "ns1.google.com",
+				Port:    53,
 				Type:    dns.StringToType["NS"],
 				Class:   1,
 				Name:    "google.com.",
@@ -76,16 +85,14 @@ func TestRealPrint(t *testing.T) {
 			},
 		},
 		{
-			Logger:    util.InitLogger(0),
-			Port:      53,
-			HTTPS:     true,
-			ShowQuery: true,
-			RD:        true,
-			ShowTTL:   true,
-			HumanTTL:  true,
-			Identify:  true,
-			XML:       true,
-			Display: util.Displays{
+			Logger: util.InitLogger(0),
+			HTTPS:  true,
+			HeaderFlags: util.HeaderFlags{
+				RD: true,
+			},
+			Identify: true,
+			XML:      true,
+			Display: util.Display{
 				Comments:       true,
 				Question:       true,
 				Answer:         true,
@@ -93,9 +100,13 @@ func TestRealPrint(t *testing.T) {
 				Additional:     true,
 				Statistics:     true,
 				UcodeTranslate: false,
+				TTL:            true,
+				HumanTTL:       true,
+				ShowQuery:      true,
 			},
 			Request: util.Request{
 				Server:  "https://dns.froth.zone/dns-query",
+				Port:    443,
 				Type:    dns.StringToType["NS"],
 				Class:   1,
 				Name:    "freecumextremist.com.",
@@ -108,14 +119,13 @@ func TestRealPrint(t *testing.T) {
 			},
 		},
 		{
-			Logger:    util.InitLogger(0),
-			Port:      853,
-			TLS:       true,
-			ShowQuery: true,
-			RD:        true,
+			Logger: util.InitLogger(0),
+			TLS:    true,
+			HeaderFlags: util.HeaderFlags{
+				RD: true,
+			},
 			Verbosity: 0,
-			ShowTTL:   false,
-			Display: util.Displays{
+			Display: util.Display{
 				Comments:       true,
 				Question:       true,
 				Answer:         true,
@@ -123,25 +133,29 @@ func TestRealPrint(t *testing.T) {
 				Additional:     true,
 				Statistics:     true,
 				UcodeTranslate: true,
+				TTL:            false,
+				ShowQuery:      true,
 			},
 			Request: util.Request{
 				Server: "dns.google",
+				Port:   853,
 				Type:   dns.StringToType["NS"],
 				Class:  1,
 				Name:   "freecumextremist.com.",
 			},
 		},
 		{
-			Logger:    util.InitLogger(0),
-			Port:      53,
-			TCP:       true,
-			ShowQuery: true,
-			AA:        true,
-			RD:        true,
+			Logger: util.InitLogger(0),
+			TCP:    true,
+
+			HeaderFlags: util.HeaderFlags{
+				AA: true,
+				RD: true,
+			},
 			Verbosity: 0,
-			ShowTTL:   true,
-			YAML:      true,
-			Display: util.Displays{
+
+			YAML: true,
+			Display: util.Display{
 				Comments:       true,
 				Question:       true,
 				Answer:         true,
@@ -149,9 +163,12 @@ func TestRealPrint(t *testing.T) {
 				Additional:     true,
 				Statistics:     true,
 				UcodeTranslate: false,
+				TTL:            true,
+				ShowQuery:      true,
 			},
 			Request: util.Request{
 				Server:  "rin.froth.zone",
+				Port:    53,
 				Type:    dns.StringToType["A"],
 				Class:   1,
 				Name:    "froth.zone.",
@@ -176,7 +193,7 @@ func TestRealPrint(t *testing.T) {
 
 			if test.JSON || test.XML || test.YAML {
 				str := ""
-				str, err = query.PrintSpecial(resp.DNS, test)
+				str, err = query.PrintSpecial(resp, test)
 				assert.NilError(t, err)
 				assert.Assert(t, str != "")
 			}
@@ -190,7 +207,7 @@ func TestRealPrint(t *testing.T) {
 func TestBadFormat(t *testing.T) {
 	t.Parallel()
 
-	_, err := query.PrintSpecial(new(dns.Msg), util.Options{})
+	_, err := query.PrintSpecial(util.Response{DNS: new(dns.Msg)}, util.Options{})
 	assert.ErrorContains(t, err, "never happen")
 }
 
diff --git a/query/query.go b/query/query.go
index 9b1cda9..6bccb34 100644
--- a/query/query.go
+++ b/query/query.go
@@ -102,7 +102,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
 	opts.Logger.Debug(req)
 
 	if !opts.Short {
-		if opts.ShowQuery {
+		if opts.Display.ShowQuery {
 			opts.Logger.Info("Printing constructed query")
 
 			var (
@@ -111,7 +111,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
 			)
 
 			if opts.JSON || opts.XML || opts.YAML {
-				str, err = PrintSpecial(req, opts)
+				str, err = PrintSpecial(util.Response{DNS: req}, opts)
 				if err != nil {
 					return util.Response{}, err
 				}
@@ -133,7 +133,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
 
 			fmt.Println(str)
 
-			opts.ShowQuery = false
+			opts.Display.ShowQuery = false
 		}
 	}
 
diff --git a/query/query_test.go b/query/query_test.go
index 6b4d504..5a5183f 100644
--- a/query/query_test.go
+++ b/query/query_test.go
@@ -16,18 +16,20 @@ func TestCreateQ(t *testing.T) {
 
 	in := []util.Options{
 		{
-			Logger:    util.InitLogger(0),
-			Port:      53,
-			Z:         true,
-			ShowQuery: true,
-			YAML:      true,
+			Logger: util.InitLogger(0),
+			HeaderFlags: util.HeaderFlags{
+				Z: true,
+			},
+
+			YAML: true,
 
 			Request: util.Request{
 				Server: "8.8.4.4",
+				Port:   53,
 				Type:   dns.TypeA,
 				Name:   "example.com.",
 			},
-			Display: util.Displays{
+			Display: util.Display{
 				Comments:   true,
 				Question:   true,
 				Opt:        true,
@@ -35,6 +37,7 @@ func TestCreateQ(t *testing.T) {
 				Authority:  true,
 				Additional: true,
 				Statistics: true,
+				ShowQuery:  true,
 			},
 			EDNS: util.EDNS{
 				ZFlag:      1,
@@ -50,18 +53,19 @@ func TestCreateQ(t *testing.T) {
 			},
 		},
 		{
-			Logger:    util.InitLogger(0),
-			Port:      53,
-			Z:         true,
-			ShowQuery: true,
-			XML:       true,
+			Logger: util.InitLogger(0),
+			HeaderFlags: util.HeaderFlags{
+				Z: true,
+			},
+			XML: true,
 
 			Request: util.Request{
 				Server: "8.8.4.4",
+				Port:   53,
 				Type:   dns.TypeA,
 				Name:   "example.com.",
 			},
-			Display: util.Displays{
+			Display: util.Display{
 				Comments:       true,
 				Question:       true,
 				Opt:            true,
@@ -70,22 +74,21 @@ func TestCreateQ(t *testing.T) {
 				Additional:     true,
 				Statistics:     true,
 				UcodeTranslate: true,
+				ShowQuery:      true,
 			},
 		},
 		{
 			Logger: util.InitLogger(0),
-			Port:   853,
-			// Z:         true,
-			ShowQuery: true,
-			JSON:      true,
-			QUIC:      true,
+			JSON:   true,
+			QUIC:   true,
 
 			Request: util.Request{
 				Server: "dns.adguard.com",
+				Port:   853,
 				Type:   dns.TypeA,
 				Name:   "example.com.",
 			},
-			Display: util.Displays{
+			Display: util.Display{
 				Comments:   true,
 				Question:   true,
 				Opt:        true,
@@ -93,6 +96,7 @@ func TestCreateQ(t *testing.T) {
 				Authority:  true,
 				Additional: true,
 				Statistics: true,
+				ShowQuery:  true,
 			},
 			EDNS: util.EDNS{
 				EnableEDNS: true,
@@ -113,7 +117,7 @@ func TestCreateQ(t *testing.T) {
 			assert.NilError(t, err)
 			assert.Assert(t, res != util.Response{})
 
-			str, err := query.PrintSpecial(res.DNS, opt)
+			str, err := query.PrintSpecial(res, opt)
 
 			assert.NilError(t, err)
 			assert.Assert(t, str != "")
diff --git a/query/resolver.go b/query/resolver.go
index 2ede1db..049522e 100644
--- a/query/resolver.go
+++ b/query/resolver.go
@@ -31,7 +31,7 @@ func LoadResolver(opts util.Options) (Resolver, error) {
 		}, nil
 	case opts.QUIC:
 		opts.Logger.Info("loading DNS-over-QUIC resolver")
-		opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
+		opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Request.Port))
 
 		return &QUICResolver{
 			opts: opts,
@@ -48,7 +48,7 @@ func LoadResolver(opts util.Options) (Resolver, error) {
 		}, nil
 	default:
 		opts.Logger.Info("loading standard/DNS-over-TLS resolver")
-		opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
+		opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Request.Port))
 
 		return &StandardResolver{
 			opts: opts,
diff --git a/query/struct.go b/query/struct.go
index 2617b4f..3ad2159 100644
--- a/query/struct.go
+++ b/query/struct.go
@@ -4,48 +4,63 @@ package query
 
 import (
 	"errors"
-	"fmt"
-	"strconv"
-	"strings"
-	"time"
-
-	"git.froth.zone/sam/awl/util"
-	"github.com/miekg/dns"
-	"golang.org/x/net/idna"
 )
 
 // Message is for overall DNS responses.
 //
-//nolint:govet // Better output is worth 32 bytes.
+//nolint:govet // Better looking output is worth a few bytes.
 type Message struct {
-	Header     dns.MsgHdr `json:"header,omitempty" xml:"header,omitempty" yaml:",omitempty"`
-	Opt        []Opts     `json:"opt,omitempty" xml:"opt,omitempty" yaml:"opt,omitempty"`
-	Question   []Question `json:"question,omitempty" xml:"question,omitempty" yaml:",omitempty"`
-	Answer     []Answer   `json:"answer,omitempty" xml:"answer,omitempty" yaml:",omitempty"`
-	Ns         []Answer   `json:"ns,omitempty" xml:"ns,omitempty" yaml:",omitempty"`
-	Additional []Answer   `json:"additional,omitempty" xml:"additional,omitempty" yaml:",omitempty"`
+	// Header section
+	Header `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
+	// Opt Pseudosection
+	Opt []Opts `json:"opt,omitempty" xml:"opt,omitempty" yaml:"opt,omitempty"`
+	// Question Section
+	Question []Question `json:"question,omitempty" xml:"question,omitempty" yaml:"question,omitempty"`
+	// Answer Section
+	Answer []Answer `json:"answer,omitempty" xml:"answer,omitempty" yaml:"answer,omitempty"`
+	// Authority Section
+	Authority []Answer `json:"authority,omitempty" xml:"authority,omitempty" yaml:"authority,omitempty"`
+	// Additional Section
+	Additional []Answer `json:"additional,omitempty" xml:"additional,omitempty" yaml:"additional,omitempty"`
+	// Statistics :)
+	Statistics `json:"statistics,omitempty" xml:"statistics,omitempty" yaml:"statistics,omitempty"`
+}
+
+// Header is the header.
+type Header struct {
+	Opcode             string `json:"opcode," xml:"opcode," yaml:"opcode" example:"QUERY"`
+	Status             string `json:"status," xml:"status," yaml:"status" example:"NOERR"`
+	ID                 uint16 `json:"id," xml:"id," yaml:"id" example:"12"`
+	Response           bool   `json:"response," xml:"response," yaml:"response" example:"true"`
+	Authoritative      bool   `json:"authoritative," xml:"authoritative," yaml:"authoritative" example:"false"`
+	Truncated          bool   `json:"truncated," xml:"truncated," yaml:"truncated" example:"false"`
+	RecursionDesired   bool   `json:"recursionDesired," xml:"recursionDesired," yaml:"recursionDesired" example:"true"`
+	RecursionAvailable bool   `json:"recursionAvailable," xml:"recursionAvailable," yaml:"recursionAvailable" example:"true"`
+	Zero               bool   `json:"zero," xml:"zero," yaml:"zero" example:"false"`
+	AuthenticatedData  bool   `json:"authenticatedData," xml:"authenticatedData," yaml:"authenticatedData" example:"false"`
+	CheckingDisabled   bool   `json:"checkingDisabled," xml:"checkingDisabled," yaml:"checkingDisabled" example:"false"`
 }
 
 // Question is a DNS Query.
 type Question struct {
-	Name  string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
-	Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"`
-	Type  string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
+	Name  string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"localhost"`
+	Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
+	Type  string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
 }
 
 // RRHeader is for DNS Resource Headers.
 type RRHeader struct {
-	Name     string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
-	TTL      string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:",omitempty"`
-	Class    string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"`
-	Type     string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
+	Name     string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"127.0.0.1"`
+	TTL      string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:"ttl,omitempty" example:"0ms"`
+	Class    string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
+	Type     string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
 	Rdlength uint16 `json:"-" xml:"-" yaml:"-"`
 }
 
 // Opts is for the OPT pseudosection, nearly exclusively for EDNS.
 type Opts struct {
-	Name  string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
-	Value string `json:"value" xml:"value" yaml:""`
+	Name  string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
+	Value string `json:"value" xml:"value" yaml:"value"`
 }
 
 // Answer is for a DNS Response.
@@ -54,199 +69,12 @@ type Answer struct {
 	RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
 }
 
-// ToString turns the response into something that looks a lot like dig
-//
-// Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900
-func ToString(res util.Response, opts util.Options) (string, error) {
-	if res.DNS == nil {
-		return "<nil> MsgHdr", errNoMessage
-	}
-
-	var (
-		s   string
-		opt *dns.OPT
-	)
-
-	if !opts.Short {
-		if opts.Display.Comments {
-			s += res.DNS.MsgHdr.String() + " "
-			s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", "
-			s += "ANSWER: " + strconv.Itoa(len(res.DNS.Answer)) + ", "
-			s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", "
-			s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n"
-			opt = res.DNS.IsEdns0()
-
-			if opt != nil && opts.Display.Opt {
-				// OPT PSEUDOSECTION
-				s += opt.String() + "\n"
-			}
-		}
-
-		if opts.Display.Question {
-			if len(res.DNS.Question) > 0 {
-				if opts.Display.Comments {
-					s += "\n;; QUESTION SECTION:\n"
-				}
-
-				for _, r := range res.DNS.Question {
-					str, err := stringParse(r.String(), false, opts)
-					if err != nil {
-						return "", fmt.Errorf("%w", err)
-					}
-
-					s += str + "\n"
-				}
-			}
-		}
-
-		if opts.Display.Answer {
-			if len(res.DNS.Answer) > 0 {
-				if opts.Display.Comments {
-					s += "\n;; ANSWER SECTION:\n"
-				}
-
-				for _, r := range res.DNS.Answer {
-					if r != nil {
-						str, err := stringParse(r.String(), true, opts)
-						if err != nil {
-							return "", fmt.Errorf("%w", err)
-						}
-
-						s += str + "\n"
-					}
-				}
-			}
-		}
-
-		if opts.Display.Authority {
-			if len(res.DNS.Ns) > 0 {
-				if opts.Display.Comments {
-					s += "\n;; AUTHORITY SECTION:\n"
-				}
-
-				for _, r := range res.DNS.Ns {
-					if r != nil {
-						str, err := stringParse(r.String(), true, opts)
-						if err != nil {
-							return "", fmt.Errorf("%w", err)
-						}
-
-						s += str + "\n"
-					}
-				}
-			}
-		}
-
-		if opts.Display.Additional {
-			if len(res.DNS.Extra) > 0 && (opt == nil || len(res.DNS.Extra) > 1) {
-				if opts.Display.Comments {
-					s += "\n;; ADDITIONAL SECTION:\n"
-				}
-
-				for _, r := range res.DNS.Extra {
-					if r != nil && r.Header().Rrtype != dns.TypeOPT {
-						str, err := stringParse(r.String(), true, opts)
-						if err != nil {
-							return "", fmt.Errorf("%w", err)
-						}
-
-						s += str + "\n"
-					}
-				}
-			}
-		}
-
-		if opts.Display.Statistics {
-			s += "\n;; Query time: " + res.RTT.String()
-			// Add extra information to server string
-			var extra string
-
-			switch {
-			case opts.TCP:
-				extra = ":" + strconv.Itoa(opts.Port) + " (TCP)"
-			case opts.TLS:
-				extra = ":" + strconv.Itoa(opts.Port) + " (TLS)"
-			case opts.HTTPS, opts.DNSCrypt:
-				extra = ""
-			case opts.QUIC:
-				extra = ":" + strconv.Itoa(opts.Port) + " (QUIC)"
-			default:
-				extra = ":" + strconv.Itoa(opts.Port) + " (UDP)"
-			}
-
-			s += "\n;; SERVER: " + opts.Request.Server + extra
-			s += "\n;; WHEN: " + time.Now().Format(time.RFC1123Z)
-			s += "\n;; MSG SIZE  rcvd: " + strconv.Itoa(res.DNS.Len()) + "\n"
-		}
-	} else {
-		// Print just the responses, nothing else
-		for i, resp := range res.DNS.Answer {
-			temp := strings.Split(resp.String(), "\t")
-			s += temp[len(temp)-1]
-
-			if opts.Identify {
-				s += " from server " + opts.Request.Server + " in " + res.RTT.String()
-			}
-
-			// Don't print newline on last line
-			if i != len(res.DNS.Answer)-1 {
-				s += "\n"
-			}
-		}
-	}
-
-	return s, nil
-}
-
-func stringParse(str string, isAns bool, opts util.Options) (string, error) {
-	split := strings.Split(str, "\t")
-
-	// Make edits if so requested
-
-	// TODO: make less ew?
-	// This exists because the question section should be left alone EXCEPT for punycode.
-
-	if isAns {
-		if !opts.ShowTTL {
-			// Remove from existence
-			split = append(split[:1], split[2:]...)
-		}
-
-		if !opts.ShowClass {
-			// Position depends on if the TTL is there or not.
-			if opts.ShowTTL {
-				split = append(split[:2], split[3:]...)
-			} else {
-				split = append(split[:1], split[2:]...)
-			}
-		}
-
-		if opts.ShowTTL && opts.HumanTTL {
-			ttl, _ := strconv.Atoi(split[1])
-			split[1] = (time.Duration(ttl) * time.Second).String()
-		}
-	}
-
-	if opts.Display.UcodeTranslate {
-		var (
-			err  error
-			semi string
-		)
-
-		if strings.HasPrefix(split[0], ";") {
-			split[0] = strings.TrimPrefix(split[0], ";")
-			semi = ";"
-		}
-
-		split[0], err = idna.ToUnicode(split[0])
-		if err != nil {
-			return "", fmt.Errorf("punycode: %w", err)
-		}
-
-		split[0] = semi + split[0]
-	}
-
-	return strings.Join(split, "\t"), nil
+// Statistics is the little bit at the bottom :).
+type Statistics struct {
+	RTT     string `json:"queryTime,omitempty" xml:"queryTime,omitempty" yaml:"queryTime,omitempty"`
+	Server  string `json:"server,omitempty" xml:"server,omitempty" yaml:"server,omitempty"`
+	When    string `json:"when,omitempty" xml:"when,omitempty" yaml:"when,omitempty"`
+	MsgSize int    `json:"msgSize,omitempty" xml:"msgSize,omitempty" yaml:"msgSize,omitempty"`
 }
 
 var errNoMessage = errors.New("no message")
diff --git a/template.mk b/template.mk
index fe820f5..0223929 100644
--- a/template.mk
+++ b/template.mk
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Template for the BSD/GNU makefiles
 
-HASH ?= `git describe --always --dirty --broken | sed 's/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"`
+HASH ?= `git describe --always --dirty --broken | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"`
 
 SOURCES ?= $(shell find . -name "*.go" -type f ! -name '*_test*')
 TEST_SOURCES ?= $(shell find . -name "*_test.go" -type f)
@@ -90,4 +90,4 @@ clean:
 .PHONY: help
 help:
 	@echo "Usage: "
-	@sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' |  sed -e 's/^/ /'
\ No newline at end of file
+	@sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' |  sed -e 's/^/ /'
diff --git a/util/options.go b/util/options.go
index 91e5f14..c406801 100644
--- a/util/options.go
+++ b/util/options.go
@@ -12,67 +12,134 @@ import (
 
 // Options is the grand structure for all query options.
 type Options struct {
-	Logger  *logawl.Logger
-	TLSHost string
+	// The logger
+	Logger *logawl.Logger `json:"-"`
+	// Host to verify TLS cert with
+	TLSHost string `json:"tlsHost" example:""`
+	// EDNS Options
 	EDNS
-	Request     Request
-	Port        int
-	Verbosity   int
-	Display     Displays
-	TC          bool
-	ShowTTL     bool
-	ShowClass   bool
-	ShowQuery   bool
-	AA          bool
-	AD          bool
-	CD          bool
-	QR          bool
-	RD          bool
-	RA          bool
-	IPv4        bool
-	Z           bool
-	Reverse     bool
-	HumanTTL    bool
-	Truncate    bool
-	Short       bool
-	Identify    bool
-	JSON        bool
-	XML         bool
-	YAML        bool
-	QUIC        bool
-	HTTPS       bool
-	TLSNoVerify bool
-	TLS         bool
-	DNSCrypt    bool
-	TCP         bool
-	IPv6        bool
+	// DNS request :)
+	Request Request
+
+	// Verbosity levels, see [logawl.AllLevels]
+	Verbosity int `json:"-" example:"0"`
+	// Display options
+	Display Display
+	// Ignore Truncation
+	Truncate bool `json:"ignoreTruncate" example:"false"`
+	// Print only the answer
+	Short bool `json:"short" example:"false"`
+	// When Short is true, display where the query came from
+	Identify bool `json:"identify" example:"false"`
+	// Perform a reverse DNS query when true
+	Reverse bool `json:"reverse" example:"false"`
+
+	HeaderFlags
+
+	// Display resposne as JSON
+	JSON bool `json:"-" xml:"-" yaml:"-"`
+	// Display response as XML
+	XML bool `json:"-" xml:"-" yaml:"-"`
+	// Display response as YAML
+	YAML bool `json:"-" xml:"-" yaml:"-"`
+
+	// Use TCP instead of UDP to make the query
+	TCP bool `json:"tcp" example:"false"`
+	// Use DNS-over-TLS to make the query
+	TLS bool `json:"dnsOverTLS" example:"false"`
+	// When using TLS, ignore certificates
+	TLSNoVerify bool `json:"tlsNoVerify" example:"false"`
+	// Use DNS-over-HTTPS to make the query
+	HTTPS bool `json:"dnsOverHTTPS" example:"false"`
+	// Use DNS-over-QUIC to make the query
+	//nolint:tagliatelle // QUIC is an acronym
+	QUIC bool `json:"dnsOverQUIC" example:"false"`
+	// Use DNSCrypt to make the query
+	DNSCrypt bool `json:"dnscrypt" example:"false"`
+
+	// Force IPv4 only
+	IPv4 bool `json:"forceIPv4" example:"false"`
+	// Force IPv6 only
+	IPv6 bool `json:"forceIPv6" example:"false"`
 }
 
-// Displays contains toggles for what to (and not to) display.
-type Displays struct {
-	Comments       bool
-	Question       bool // QUESTION SECTION
-	Opt            bool // OPT PSEUDOSECTION
-	Answer         bool // ANSWER SECTION
-	Authority      bool // AUTHORITY SECTION
-	Additional     bool // ADDITIONAL SECTION
-	Statistics     bool // Query time, message size, etc.
-	UcodeTranslate bool // Translate Punycode back to Unicode
+// HeaderFlags are the flags that are in DNS headers.
+type HeaderFlags struct {
+	// Authoritative Answer DNS query flag
+	AA bool `json:"authoritative" example:"false"`
+	// Authenticated Data DNS query flag
+	AD bool `json:"authenticatedData" example:"false"`
+	// Checking Disabled DNS query flag
+	CD bool `json:"checkingDisabled" example:"false"`
+	// QueRy DNS query flag
+	QR bool `json:"query" example:"false"`
+	// Recursion Desired DNS query flag
+	RD bool `json:"recursionDesired" example:"true"`
+	// Recursion Available DNS query flag
+	RA bool `json:"recursionAvailable" example:"false"`
+	// TrunCated DNS query flag
+	TC bool `json:"truncated" example:"false"`
+	// Zero DNS query flag
+	Z bool `json:"zero" example:"false"`
+}
+
+// Display contains toggles for what to (and not to) display.
+type Display struct {
+	/* Section displaying */
+
+	// Comments?
+	Comments bool `json:"comments" example:"true"`
+	// QUESTION SECTION
+	Question bool `json:"question" example:"true"`
+	// OPT PSEUDOSECTION
+	Opt bool `json:"opt" example:"true"`
+	// ANSWER SECTION
+	Answer bool `json:"answer" example:"true"`
+	// AUTHORITY SECTION
+	Authority bool `json:"authority" example:"true"`
+	// ADDITIONAL SECTION
+	Additional bool `json:"additional" example:"true"`
+	// Query time, message size, etc.
+	Statistics bool `json:"statistics" example:"true"`
+	// Display TTL in response
+	TTL bool `json:"ttl" example:"true"`
+
+	/* Answer formatting */
+
+	// Display Class in response
+	ShowClass bool `json:"showClass" example:"true"`
+	// Display query before it is sent
+	ShowQuery bool `json:"showQuery" example:"false"`
+	// Display TTL as human-readable
+	HumanTTL bool `json:"humanTTL" example:"false"`
+	// Translate Punycode back to Unicode
+	UcodeTranslate bool `json:"unicode" example:"true"`
 }
 
 // EDNS contains toggles for various EDNS options.
 type EDNS struct {
-	Subnet     dns.EDNS0_SUBNET
-	ZFlag      uint16
-	BufSize    uint16
-	EnableEDNS bool
-	Cookie     bool
-	DNSSEC     bool
-	Expire     bool
-	KeepOpen   bool
-	Nsid       bool
-	Padding    bool
-	Version    uint8
+	// Subnet to originate query from.
+	Subnet dns.EDNS0_SUBNET `json:"subnet"`
+	// Must Be Zero flag
+	ZFlag uint16 `json:"zflag" example:"0"`
+	// UDP buffer size
+	BufSize uint16 `json:"bufSize" example:"1232"`
+	// Enable/Disable EDNS entirely
+	EnableEDNS bool `json:"edns" example:"false"`
+	// Sending EDNS cookie
+	Cookie bool `json:"cookie" example:"true"`
+	// Enabling DNSSEC
+	DNSSEC bool `json:"dnssec" example:"false"`
+	// Sending EDNS Expire
+	Expire bool `json:"expire" example:"false"`
+	// Sending EDNS TCP keepopen
+	KeepOpen bool `json:"keepOpen" example:"false"`
+	// Sending EDNS NSID
+	Nsid bool `json:"nsid" example:"false"`
+	// Send EDNS Padding
+	Padding bool `json:"padding" example:"false"`
+	// Set EDNS version (default: 0)
+	Version uint8 `json:"version" example:"0"`
 }
 
 // ParseSubnet takes a subnet argument and makes it into one that the DNS library
diff --git a/util/query.go b/util/query.go
index 20851b6..8e1dd72 100644
--- a/util/query.go
+++ b/util/query.go
@@ -10,16 +10,26 @@ import (
 
 // Response is the DNS response.
 type Response struct {
-	DNS *dns.Msg      // The full DNS response
-	RTT time.Duration `json:"rtt"` // The time it took to make the DNS query
+	// The full DNS response
+	DNS *dns.Msg `json:"response"`
+	// The time it took to make the DNS query
+	RTT time.Duration `json:"rtt" example:"2000000000"`
 }
 
 // Request is a structure for a DNS query.
 type Request struct {
-	Server  string `json:"server"`
-	Name    string `json:"name"`
-	Timeout time.Duration
-	Retries int
-	Type    uint16 `json:"request"`
-	Class   uint16 `json:"class"`
+	// Server to query
+	Server string `json:"server" example:"1.0.0.1"`
+	// Domain to query
+	Name string `json:"name" example:"example.com"`
+	// Duration to wait until marking request as failed
+	Timeout time.Duration `json:"timeout" example:"2000000000"`
+	// Port to make DNS request on
+	Port int `json:"port" example:"53"`
+	// Number of failures to make before giving up
+	Retries int `json:"retries" example:"2"`
+	// Request type, eg. A, AAAA, NAPTR
+	Type uint16 `json:"type" example:"1"`
+	// Request class, eg. IN
+	Class uint16 `json:"class" example:"1"`
 }
