Menu
Coddy logo textTech

Recap - File Parser

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 58 of 107.

challenge icon

Challenge

Easy

Let's build a configuration file parser that brings together all the error handling techniques from this chapter. Your parser will handle multiple failure scenarios with custom error types, sentinel errors, error wrapping, and proper cleanup using defer.

You'll organize your code across three files:

  • errors.go: Define your error infrastructure for the parser.

    Create two sentinel errors:

    • ErrEmptyContent with message "file content is empty"
    • ErrInvalidFormat with message "invalid configuration format"

    Create a custom error type ParseError with Line (int) and Message (string) fields. Its Error() method should return: parse error at line [Line]: [Message]

  • parser.go: Build the configuration parser with layered error handling.

    Create a ConfigParser struct with a parsed boolean field to track whether parsing completed successfully.

    Implement these methods:

    • NewConfigParser() *ConfigParser - creates a new parser with parsed set to false
    • ParseLine(lineNum int, line string) error - validates a single line. If the line doesn't contain an = character, return a *ParseError with the line number and message "missing key-value separator". Otherwise return nil.
    • Parse(content string) error - the main parsing method that:
      • Returns ErrEmptyContent if content is empty
      • Returns ErrInvalidFormat if content doesn't contain at least one newline character
      • Splits content by newlines and calls ParseLine for each line (line numbers start at 1)
      • If ParseLine returns an error, wrap it with "parsing configuration: %w" and return
      • Sets parsed to true on success and returns nil
    • Status() string - returns "ready" if parsed is true, otherwise "not parsed"
  • main.go: Read configuration content and demonstrate comprehensive error inspection.

    Read two lines of input that together form the configuration content (join them with a newline). Use defer to always print the parser's status at the end, regardless of success or failure.

    After attempting to parse, inspect any error using both errors.Is() and errors.As() to provide specific feedback based on what went wrong.

The following inputs will be provided:

  • Line 1: First line of configuration content
  • Line 2: Second line of configuration content

Handle errors with specific messages:

  • If errors.Is() finds ErrEmptyContent: print Error: No configuration provided
  • If errors.Is() finds ErrInvalidFormat: print Error: Configuration must have multiple lines
  • If errors.As() finds a *ParseError: print the full wrapped error, then on a new line print Fix line [Line]: [Message]
  • If parsing succeeds: print Configuration parsed successfully

After handling the result (success or error), the deferred status should print on a new line: Parser status: [status]

For example, given host=localhost and port=8080, your output should be:

Configuration parsed successfully
Parser status: ready

And given host=localhost and invalid line, your output should be:

parsing configuration: parse error at line 2: missing key-value separator
Fix line 2: missing key-value separator
Parser status: not parsed

Try it yourself

package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Read first line of configuration
	line1, _ := reader.ReadString('\n')
	line1 = line1[:len(line1)-1] // Remove trailing newline
	
	// Read second line of configuration
	line2, _ := reader.ReadString('\n')
	if len(line2) > 0 && line2[len(line2)-1] == '\n' {
		line2 = line2[:len(line2)-1]
	}
	
	// Join the two lines with a newline to form the configuration content
	content := line1 + "\n" + line2
	
	// Create a new parser
	parser := NewConfigParser()
	
	// TODO: Use defer to always print the parser's status at the end
	// Format: "Parser status: [status]"
	
	// TODO: Attempt to parse the content
	
	// TODO: Handle errors using errors.Is() and errors.As()
	// - If errors.Is() finds ErrEmptyContent: print "Error: No configuration provided"
	// - If errors.Is() finds ErrInvalidFormat: print "Error: Configuration must have multiple lines"
	// - If errors.As() finds a *ParseError: print the full wrapped error,
	//   then on a new line print "Fix line [Line]: [Message]"
	// - If parsing succeeds: print "Configuration parsed successfully"
	
	_ = errors.Is // hint: use errors.Is for sentinel errors
	_ = errors.As // hint: use errors.As for custom error types
	_ = fmt.Println
}

All lessons in Object Oriented Programming