master de70ca622e41 cached
656 files
1.5 MB
436.7k tokens
3579 symbols
1 requests
Download .txt
Showing preview only (1,737K chars total). Download the full file or copy to clipboard to get everything.
Repository: PacktPublishing/Hands-On-Dependency-Injection-in-Go
Branch: master
Commit: de70ca622e41
Files: 656
Total size: 1.5 MB

Directory structure:
gitextract_t7lucnow/

├── .gitignore
├── LICENSE
├── README.md
├── ch01/
│   ├── 01_defining_depenency_injection/
│   │   ├── 01_interface.go
│   │   ├── 02_function_literal.go
│   │   ├── 03_test_without_nfs_test.go
│   │   └── 04_fail_test_without_nfs_test.go
│   └── 02_code_smells/
│       ├── 01_code_bloat/
│       │   └── 01_switch_type.go
│       ├── 02_resistance_to_change/
│       │   └── 01_shotgun_surgey.go
│       ├── 03_wasted_effort/
│       │   ├── 01_excessive_comments.go
│       │   └── 02_complicated_go.go
│       └── 04_tight_coupling/
│           ├── 01_circular_dependencies/
│           │   ├── config/
│           │   │   └── config.go
│           │   └── payment/
│           │       └── currency.go
│           ├── 02_object_orgy.go
│           └── 03_feature_envy.go
├── ch02/
│   ├── 01_single_responsibility_principle/
│   │   ├── 01_responsibility_vs_change.go
│   │   ├── 02_responsibility_vs_change.go
│   │   ├── 03_responsibility_vs_change.go
│   │   ├── 04_long_method.go
│   │   ├── 04_long_method_test.go
│   │   └── 05_srp_method.go
│   ├── 02_open_closed_principle/
│   │   ├── 01_open_closed_failure.go
│   │   ├── 02_open_closed_success.go
│   │   ├── 03_shotgun_surgery.go
│   │   ├── 04_after_shotgun_surgery.go
│   │   ├── 05_composition.go
│   │   ├── 06_handler_struct.go
│   │   └── 07_handler_func.go
│   ├── 03_liskov_substitution_principle/
│   │   ├── 01_violation/
│   │   │   └── example.go
│   │   ├── 02_fixed/
│   │   │   └── example.go
│   │   ├── 03_fixed/
│   │   │   └── example.go
│   │   ├── 04_behaviour.go
│   │   └── 05_behaviour_fixed.go
│   └── 04_interface_segregation_principle/
│       ├── 01_fat_interface.go
│       ├── 02_thin_interface.go
│       ├── 03_repeated_inputs.go
│       ├── 04_repeated_inputs.go
│       ├── 05_repeated_inputs.go
│       └── 06_implicit_interfaces.go
├── ch03/
│   ├── 01_optimizing_for_humans/
│   │   ├── 01_not_so_simple.go
│   │   ├── 02_start_simple.go
│   │   ├── 03_too_abstract.go
│   │   ├── 04_common_concept.go
│   │   ├── 05_boolean_param.go
│   │   ├── 06_hidden_boolean.go
│   │   ├── 07_wide_formatter.go
│   │   ├── 08_thin_formatters.go
│   │   └── 09_extra_config.go
│   ├── 02_unit_tests/
│   │   ├── 01_loader.go
│   │   ├── 02_language_feature.go
│   │   ├── 03_simple_test.go
│   │   ├── 04_test_from_api.go
│   │   ├── 05_repeated_code.go
│   │   ├── 06_tdt.go
│   │   ├── 07_person_loader.go
│   │   ├── 08_stub.go
│   │   ├── 09_stub_tdt.go
│   │   └── 10_mocks.go
│   ├── 03_test_induced_damage/
│   │   ├── 01_io_closer.go
│   │   └── 02_json.go
│   ├── 04_visualizing_dependencies/
│   │   └── depgraph.sh
│   └── fake.go
├── ch04/
│   ├── 01_welcome/
│   │   ├── 01_bad_names.go
│   │   ├── 02_improved_names.go
│   │   ├── 03_long_method.go
│   │   ├── 04_long_method_test.go
│   │   └── 05_short_methods.go
│   ├── 03_known_issues/
│   │   ├── 01_data_and_rest/
│   │   │   └── get_example.go
│   │   └── 02_config_coupling/
│   │       ├── config.go
│   │       └── currency/
│   │           └── currency.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── common_test.go
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch05/
│   ├── 02_advantages/
│   │   ├── 01_function.go
│   │   ├── 02_monkey_patched.go
│   │   ├── 03_injected_lambda.go
│   │   ├── 04_as_object.go
│   │   ├── 05_math_rand.go
│   │   └── 06_math_rand_test.go
│   ├── 03_applying/
│   │   ├── 01_simple_sqlmock_test.go
│   │   └── 02_load.go
│   ├── 04_disadvantages/
│   │   ├── 01_verbose.go
│   │   ├── 02_verbose_test.go
│   │   └── 03_refactored_test.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── common_test.go
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch06/
│   ├── 01_constructor_injection/
│   │   ├── 01_welcome_email.go
│   │   ├── 01_welcome_email_test.go
│   │   ├── 02_mailer_interface.go
│   │   ├── 03_sender_interface.go
│   │   └── 05_duck_typing.go
│   ├── 02_advantages/
│   │   ├── 01_easy_to_implement.go
│   │   ├── 01_easy_to_implement_example_test.go
│   │   ├── 02_easy_to_implement.go
│   │   ├── 02_easy_to_implement_example_test.go
│   │   ├── 03_predictable.go
│   │   ├── 04_predictable.go
│   │   ├── 05_encapsulation.go
│   │   └── 06_encapsulation.go
│   ├── 03_applying/
│   │   ├── 01/
│   │   │   ├── 01_register_handler_before.go
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   └── register/
│   │   │       └── register.go
│   │   ├── 02/
│   │   │   ├── 01_register_handler.go
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   └── register/
│   │   │       └── register.go
│   │   ├── 03/
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   ├── mock_register_model_test.go
│   │   │   └── register_test.go
│   │   ├── 04/
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   ├── mock_register_model_test.go
│   │   │   ├── register.go
│   │   │   └── register_test.go
│   │   └── 05/
│   │       ├── data/
│   │       │   └── person.go
│   │       ├── fakes.go
│   │       ├── get/
│   │       │   └── getter.go
│   │       ├── list/
│   │       │   └── lister.go
│   │       ├── register/
│   │       │   └── registerer.go
│   │       └── server.go
│   ├── 04_disadvantages/
│   │   ├── 01_lots_of_changes.go
│   │   ├── 02_overuse.go
│   │   ├── 03_non_obvious.go
│   │   ├── 04_non_obvious_example_test.go
│   │   └── 05_constructors.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   ├── fake.go
│   └── pcov-html
├── ch07/
│   ├── 01_method_injection/
│   │   ├── 01_fprint.go
│   │   ├── 02_http_request.go
│   │   ├── 03_fprint.go
│   │   ├── 04_http_request.go
│   │   ├── 05_timestamp_writer_v1.go
│   │   ├── 06_timestamp_writer_v2.go
│   │   └── 07_timestamp_writer_v3.go
│   ├── 02_advantages/
│   │   ├── 01_handler_v1.go
│   │   ├── 02_handler_v2.go
│   │   ├── 03_handler_v3.go
│   │   ├── 04_context_influence.go
│   │   └── 05_person_loader.go
│   ├── 04_disadvantages/
│   │   ├── 01_data_struct.go
│   │   ├── 02_ux_improvement.go
│   │   ├── 03_many_params.go
│   │   └── 04_many_params_v2.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch08/
│   ├── 01_config_injection/
│   │   ├── 01_long_constructor.go
│   │   ├── 02_by_config_example.go
│   │   └── 03_shared_params.go
│   ├── 02_advantages/
│   │   ├── 01_injected_config/
│   │   │   ├── 01.go
│   │   │   └── 01_test.go
│   │   ├── 02_config_injection/
│   │   │   ├── 02.go
│   │   │   └── 02_test.go
│   │   ├── 03_long_constructor.go
│   │   ├── 04_by_config_example.go
│   │   ├── config/
│   │   │   └── config.go
│   │   ├── logging/
│   │   │   └── logger.go
│   │   └── stats/
│   │       └── stats.go
│   ├── 03_applying/
│   │   ├── 01_define_register_config.go
│   │   ├── 02_register_with_config_injection.go
│   │   ├── 03_model_before_data_changes.go
│   │   ├── 04_test_config_link_to_config_package.go
│   │   ├── 05_result_payload.json
│   │   └── 06_simple_test_server.go
│   ├── 04_disadvantages/
│   │   ├── 01_leaking_details.go
│   │   ├── 02_hiding_details.go
│   │   ├── 03_unclear_lifecycle.go
│   │   ├── 04_clear_lifecycle.go
│   │   └── 05_layers.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch09/
│   ├── 01_jit_injection/
│   │   ├── 01_injecting_db.go
│   │   ├── 01_injecting_db_test.go
│   │   ├── 02_injecting_business_logic.go
│   │   ├── 03_injecting_db_jit.go
│   │   ├── 03_injecting_db_jit_test.go
│   │   └── 04_noop_debugger.go
│   ├── 02_advantages/
│   │   ├── 01_long_constructor.go
│   │   ├── 02_short_constructor.go
│   │   ├── 03_optional_dep_without_jitdi.go
│   │   ├── 04_optional_dep_with_jitdi.go
│   │   ├── 05_loader.go
│   │   ├── 06_global_variable/
│   │   │   └── 06_global_variable.go
│   │   ├── 07_global_variable_jit/
│   │   │   ├── 07_global_variable_jit.go
│   │   │   └── 07_global_variable_jit_test.go
│   │   ├── 08_car_v1.go
│   │   └── 09_car_v2.go
│   ├── 03_applying/
│   │   ├── 01_commands.sh
│   │   ├── 02_coverage.txt
│   │   └── 03_initial_dao.go
│   ├── 04_disadvantages/
│   │   ├── 01_uncertain_init_state.go
│   │   ├── 02_certain_init_state.go
│   │   ├── 03_cpool_slow_constructor.go
│   │   └── 04_get_pool_with_once.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── dao.go
│   │   │   │   │   ├── data.go
│   │   │   │   │   ├── data_test.go
│   │   │   │   │   └── tracker.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   ├── go_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   ├── list_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   └── register/
│   │   │   │       ├── mock_my_saver_test.go
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch10/
│   ├── 01_intro_to_wire/
│   │   ├── 01_simple/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 02_params/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 03_error/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   └── 04_without_pset/
│   │       ├── main.go
│   │       ├── wire.go
│   │       └── wire_gen.go
│   ├── 02_advantages/
│   │   ├── 01_dig/
│   │   │   └── main.go
│   │   └── 02_instantiation_order/
│   │       ├── handler.go
│   │       ├── injectors.go
│   │       ├── main.go
│   │       ├── model.go
│   │       ├── providers.go
│   │       └── wire_gen.go
│   ├── 03_applying/
│   │   ├── 01_before_config/
│   │   │   └── main.go
│   │   ├── 02_after_config/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 03_after_exchange/
│   │   │   ├── main.go
│   │   │   └── wire.go
│   │   ├── 04_after_model/
│   │   │   ├── main.go
│   │   │   └── wire.go
│   │   ├── 05_after_rest/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 06_build_tag.go
│   │   ├── 06_build_tag_inverse.go
│   │   └── 06_main.go
│   ├── 04_disadvantages/
│   │   └── 01_complexity/
│   │       └── main.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── dao.go
│   │   │   │   │   ├── data.go
│   │   │   │   │   ├── data_test.go
│   │   │   │   │   └── tracker.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   ├── go_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   ├── list_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   └── register/
│   │   │   │       ├── mock_my_saver_test.go
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   ├── main.go
│   │   ├── main_test.go
│   │   ├── wire.go
│   │   └── wire_gen.go
│   └── fake.go
├── ch11/
│   ├── 01_di_induced_damage/
│   │   ├── 01_long_param/
│   │   │   └── 01_long_param.go
│   │   ├── 02_long_param/
│   │   │   └── 01_long_param.go
│   │   ├── 03_long_param/
│   │   │   └── 01_long_param.go
│   │   ├── 04_long_param/
│   │   │   ├── 01_long_param.go
│   │   │   └── 01_long_param_test.go
│   │   ├── 05_inject_sql/
│   │   │   └── 01_interface.go
│   │   ├── 06_inject_sql/
│   │   │   ├── 01_interface.go
│   │   │   ├── 02_implementation.go
│   │   │   ├── 02_implementation_test.go
│   │   │   ├── dao.go
│   │   │   ├── data.go
│   │   │   └── data_test.go
│   │   ├── 07_needless_indirection/
│   │   │   └── example_test.go
│   │   ├── 08_needless_indirection/
│   │   │   ├── 01_mux.go
│   │   │   ├── 01_mux_test.go
│   │   │   └── mock_my_mux_test.go
│   │   ├── 09_needless_indirection/
│   │   │   ├── 01_mux.go
│   │   │   └── 01_mux_test.go
│   │   ├── 10_needless_indirection/
│   │   │   ├── 01_mux_e2e.go
│   │   │   └── 01_mux_e2e_test.go
│   │   └── 11_service_locator/
│   │       ├── 01_service_locator.go
│   │       └── 02_usage.go
│   ├── 02_premature_future/
│   │   └── get.go
│   ├── 03_mocking_http_requests/
│   │   ├── converter.go
│   │   ├── converter_test.go
│   │   └── mock_requester_test.go
│   └── acme/
│       ├── internal/
│       │   ├── config/
│       │   │   ├── config.go
│       │   │   └── config_test.go
│       │   ├── logging/
│       │   │   └── logging.go
│       │   ├── modules/
│       │   │   ├── data/
│       │   │   │   ├── dao.go
│       │   │   │   ├── data.go
│       │   │   │   ├── data_test.go
│       │   │   │   └── tracker.go
│       │   │   ├── exchange/
│       │   │   │   ├── converter.go
│       │   │   │   ├── converter_ext_bounday_test.go
│       │   │   │   └── converter_int_bounday_test.go
│       │   │   ├── get/
│       │   │   │   ├── get.go
│       │   │   │   ├── go_test.go
│       │   │   │   └── mock_my_loader_test.go
│       │   │   ├── list/
│       │   │   │   ├── list.go
│       │   │   │   ├── list_test.go
│       │   │   │   └── mock_my_loader_test.go
│       │   │   └── register/
│       │   │       ├── mock_exchanger_test.go
│       │   │       ├── mock_my_saver_test.go
│       │   │       ├── register.go
│       │   │       └── register_test.go
│       │   └── rest/
│       │       ├── get.go
│       │       ├── get_test.go
│       │       ├── list.go
│       │       ├── list_test.go
│       │       ├── mock_get_model_test.go
│       │       ├── mock_list_model_test.go
│       │       ├── mock_register_model_test.go
│       │       ├── not_found.go
│       │       ├── not_found_test.go
│       │       ├── register.go
│       │       ├── register_test.go
│       │       └── server.go
│       ├── main.go
│       ├── main_test.go
│       ├── wire.go
│       └── wire_gen.go
├── ch12/
│   ├── 01_improvements/
│   │   └── 01_test_logging_test.go
│   ├── 03_testing/
│   │   ├── 01_mock_get_model.go
│   │   ├── 02_coverage_ch04.txt
│   │   ├── 03_coverage_ch11.txt
│   │   ├── 04_coverage_config.htm
│   │   ├── 04_coverage_data.htm
│   │   ├── 04_coverage_exchange.htm
│   │   ├── 04_coverage_get.htm
│   │   ├── 04_coverage_list.htm
│   │   ├── 04_coverage_main.htm
│   │   ├── 04_coverage_register.htm
│   │   └── 04_coverage_rest.htm
│   ├── 04_new_service/
│   │   └── 01_data_with_cache/
│   │       ├── dao.go
│   │       ├── data.go
│   │       └── internal/
│   │           ├── cache/
│   │           │   └── cache.go
│   │           └── logging/
│   │               └── logging.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── dao.go
│   │   │   │   │   ├── data.go
│   │   │   │   │   ├── data_test.go
│   │   │   │   │   └── tracker.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   ├── go_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   ├── list_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   └── register/
│   │   │   │       ├── mock_exchanger_test.go
│   │   │   │       ├── mock_my_saver_test.go
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   ├── main.go
│   │   ├── main_test.go
│   │   ├── wire.go
│   │   └── wire_gen.go
│   └── fake.go
├── default-config.json
├── fake.go
├── resources/
│   └── create.sql
└── vendor/
    ├── github.com/
    │   ├── DATA-DOG/
    │   │   └── go-sqlmock/
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── argument.go
    │   │       ├── driver.go
    │   │       ├── expectations.go
    │   │       ├── expectations_before_go18.go
    │   │       ├── expectations_go18.go
    │   │       ├── result.go
    │   │       ├── rows.go
    │   │       ├── rows_go18.go
    │   │       ├── sqlmock.go
    │   │       ├── sqlmock_go18.go
    │   │       ├── statement.go
    │   │       └── util.go
    │   ├── davecgh/
    │   │   └── go-spew/
    │   │       ├── LICENSE
    │   │       └── spew/
    │   │           ├── bypass.go
    │   │           ├── bypasssafe.go
    │   │           ├── common.go
    │   │           ├── config.go
    │   │           ├── doc.go
    │   │           ├── dump.go
    │   │           ├── format.go
    │   │           └── spew.go
    │   ├── go-sql-driver/
    │   │   └── mysql/
    │   │       ├── AUTHORS
    │   │       ├── CHANGELOG.md
    │   │       ├── CONTRIBUTING.md
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── appengine.go
    │   │       ├── buffer.go
    │   │       ├── collations.go
    │   │       ├── connection.go
    │   │       ├── connection_go18.go
    │   │       ├── const.go
    │   │       ├── driver.go
    │   │       ├── dsn.go
    │   │       ├── errors.go
    │   │       ├── fields.go
    │   │       ├── infile.go
    │   │       ├── packets.go
    │   │       ├── result.go
    │   │       ├── rows.go
    │   │       ├── statement.go
    │   │       ├── transaction.go
    │   │       ├── utils.go
    │   │       ├── utils_go17.go
    │   │       └── utils_go18.go
    │   ├── google/
    │   │   └── wire/
    │   │       ├── AUTHORS
    │   │       ├── CODE_OF_CONDUCT.md
    │   │       ├── CONTRIBUTING.md
    │   │       ├── CONTRIBUTORS
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── go.mod
    │   │       ├── go.sum
    │   │       └── wire.go
    │   ├── gorilla/
    │   │   ├── context/
    │   │   │   ├── LICENSE
    │   │   │   ├── README.md
    │   │   │   ├── context.go
    │   │   │   └── doc.go
    │   │   └── mux/
    │   │       ├── ISSUE_TEMPLATE.md
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── context_gorilla.go
    │   │       ├── context_native.go
    │   │       ├── doc.go
    │   │       ├── middleware.go
    │   │       ├── mux.go
    │   │       ├── regexp.go
    │   │       ├── route.go
    │   │       └── test_helpers.go
    │   ├── pmezard/
    │   │   └── go-difflib/
    │   │       ├── LICENSE
    │   │       └── difflib/
    │   │           └── difflib.go
    │   └── stretchr/
    │       ├── objx/
    │       │   ├── LICENSE.md
    │       │   ├── README.md
    │       │   ├── accessors.go
    │       │   ├── constants.go
    │       │   ├── conversions.go
    │       │   ├── doc.go
    │       │   ├── map.go
    │       │   ├── mutations.go
    │       │   ├── security.go
    │       │   ├── tests.go
    │       │   ├── type_specific_codegen.go
    │       │   └── value.go
    │       └── testify/
    │           ├── LICENSE
    │           ├── assert/
    │           │   ├── assertion_format.go
    │           │   ├── assertion_format.go.tmpl
    │           │   ├── assertion_forward.go
    │           │   ├── assertion_forward.go.tmpl
    │           │   ├── assertions.go
    │           │   ├── doc.go
    │           │   ├── errors.go
    │           │   ├── forward_assertions.go
    │           │   └── http_assertions.go
    │           ├── mock/
    │           │   ├── doc.go
    │           │   └── mock.go
    │           └── require/
    │               ├── doc.go
    │               ├── forward_requirements.go
    │               ├── require.go
    │               ├── require.go.tmpl
    │               ├── require_forward.go
    │               ├── require_forward.go.tmpl
    │               └── requirements.go
    ├── go.uber.org/
    │   └── dig/
    │       ├── CHANGELOG.md
    │       ├── LICENSE
    │       ├── Makefile
    │       ├── README.md
    │       ├── check_license.sh
    │       ├── cycle.go
    │       ├── dig.go
    │       ├── doc.go
    │       ├── error.go
    │       ├── glide.yaml
    │       ├── internal/
    │       │   ├── digreflect/
    │       │   │   └── func.go
    │       │   └── dot/
    │       │       └── graph.go
    │       ├── param.go
    │       ├── result.go
    │       ├── stringer.go
    │       ├── types.go
    │       └── version.go
    ├── google.golang.org/
    │   └── appengine/
    │       ├── LICENSE
    │       └── cloudsql/
    │           ├── cloudsql.go
    │           ├── cloudsql_classic.go
    │           └── cloudsql_vm.go
    └── vendor.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

config.json


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Packt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

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.


================================================
FILE: README.md
================================================



# Hands-On Dependency Injection in Go

<a href="https://www.packtpub.com/application-development/hands-dependency-injection-go?utm_source=github&utm_medium=repository&utm_campaign=9781789132762 "><img src="https://d255esdrn735hr.cloudfront.net/sites/default/files/imagecache/ppv4_main_book_cover/B10763_MockupCover_new.png" alt="Hands-On Dependency Injection in Go" height="256px" align="right"></a>

This is the code repository for [Hands-On Dependency Injection in Go](https://www.packtpub.com/application-development/hands-dependency-injection-go?utm_source=github&utm_medium=repository&utm_campaign=9781789132762 ), published by Packt.

**Develop clean Go code that is easier to read, maintain, and test**

## What is this book about?
Hands-On Dependency Injection in Go takes you on a journey, refactoring existing code to adopt dependency injection (DI) using various methods available in Go.

This book covers the following exciting features:
* Understand the benefits of dependency injection 
* Explore SOLID design principles and how they relate to Go 
* Analyze various dependency injection patterns available in Go 
* Leverage DI to produce high quality, loosely coupled Go code 
* Refactor existing Go code to adopt dependency injection 
* Discover tools to improve your code's testability and test coverage 
* Generate and interpret Go dependency graphs 

If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1789132762) today!

<a href="https://www.packtpub.com/?utm_source=github&utm_medium=banner&utm_campaign=GitHubBanner"><img src="https://raw.githubusercontent.com/PacktPublishing/GitHub/master/GitHub.png" 
alt="https://www.packtpub.com/" border="5" /></a>

## Instructions and Navigations
All of the code is organized into folders. For example, ch02.

The code will look like the following:
```
html, body, #map {
 height: 100%; 
 margin: 0;
 padding: 0
}
```

**Following is what you need for this book:**
Hands-On Dependency Injection in Go is for programmers with a few year s experience in any language and a basic understanding of Go. If you wish to produce clean, loosely coupled code that is inherently easier to test, this book is for you.

## Getting the source

The easiest way to obtain the source code is to use `go get`.  
This will ensure that the code is placed in the correct directory and should be then runnable and testable.

To download this repo use `go get github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/...`

## Code Organization

In this repository, there is 1 folder for every chapter of the book, named chXX where XX is the chapter number.

The code provided are expanded versions of the code presented in the book.  While it will compile and typically
will not throw an error when passed into `go test` it is not designed to be executed.

From chapter 4 onwards, there is an `acme` directory included with the code that chapter.
The `acme` directory is the code for the sample service presented in the book with the changes discussed in that chapter
already applied.

You will also find 2 additional directories in the root of the repository:
 
 * **resources** - this directory contains an SQL file that should be used to populate a MySQL database.  This database
 is used by the sample service 
 * **vendor** - this is standard go vendor directory which contains the external packages required by the sample service

## Setting up the MySQL database

The easiest way to create and populate the database required by the sample service is by running the following:

`mysql < ./resources/create.sql`

Depending on your settings you may want to provide a username and password like this:

`mysql -u [your username] -p < ./resources/create.sql`

This will create a database called `acme` with 1 table and 4 records.

## Creating a free account on CurrencyLayer

The sample service uses a free currency conversion service.  In order to successfully run all the examples, you will need
to sign up [here](https://currencylayer.com/) and obtain an API Key.

## Configuring the sample service

Now that you have your MySQL and CurrencyLayer credentials you can create a config for the sample service.

1. Copy `default-config.json` (found next to this file) to `config.json`
1. Open `config.json` in your favorite editor
1. Add your database credentials to the `"dsn"` setting.  Should be in the form: 
`"[username]:[password]@tcp(localhost:3306)/[database name]?autocommit=true"`
1. Add your API Key to the `"exchangeRateAPIKey"` setting.  Should be in the form: `"1234567890abcdef1234567890abcdef"`

## Running the sample service for a particular chapter

To run sample service for a particular chapter:
 
1. First make sure you are in the base of this repository:
`cd $GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/` 
1. Use a command similar to the following (which is for ch04):
`ACME_CONFIG=$GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/config.json go run ./ch04/acme/main.go` 

### Special instructions for chapters 10-12

As we have multiple files and tests in the `main` package, we cannot use the standard `go run ./ch10/acme/main.go` to run the service.

Instead we need to modify the command to `go run ./ch10/acme/main.go ./ch10/acme/wire_gen.go`

## Running tests for a chapter

To run sample service for a particular chapter you can use a command similar to the follow (which is for ch04):

1. First make sure you are in the base of this repository:
`cd $GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/` 
1. Use a command similar to the following (which is for ch04):
`ACME_CONFIG=$GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/config.json go test ./ch04/...` 

With the following software and hardware list you can run all code files present in the book (Chapter 1-12).
### Software and Hardware List
| Chapter  | Software required                    | OS required                        |
| -------- | ------------------------------------ | -----------------------------------|
| 1-12     | Go 1.10.x+                           | Windows, Mac OS X, and Linux (Any) |
| 4-12     | MySQL 5.7.x+                         | Windows, Mac OS X, and Linux (Any) |
| 4-12     | CurrencyLayer                        | Windows, Mac OS X, and Linux (Any) |

### Related products
* Mastering Go [[Packt]](https://www.packtpub.com/networking-and-servers/mastering-go?utm_source=github&utm_medium=repository&utm_campaign=9781788626545 ) [[Amazon]](https://www.amazon.com/dp/1788626540)

* Go Standard Library Cookbook [[Packt]](https://www.packtpub.com/application-development/go-standard-library-cookbook?utm_source=github&utm_medium=repository&utm_campaign=9781788475273 ) [[Amazon]](https://www.amazon.com/dp/1788475275)


## Get to Know the Author
**Corey Scott**
is a senior software engineer currently living in Melbourne, Australia. He’s been programming professionally since 2000, with the last 5 years spent building large-scale distributed services in Go.
An occasional technical speaker and blogger on a variety of software-related topics, he is passionate about designing and building quality software. He believes that software engineering is a craft that should be honed, debated, and continuously improved. He takes a pragmatic, non-zealot approach to coding and is always up for a good debate about software engineering, continuous delivery, testing, or clean coding.


## Other books by the authors


### Suggestions and Feedback
[Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions.


### Download a free PDF

 <i>If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.<br>Simply click on the link to claim your free PDF.</i>
<p align="center"> <a href="https://packt.link/free-ebook/9781789132762">https://packt.link/free-ebook/9781789132762 </a> </p>

================================================
FILE: ch01/01_defining_depenency_injection/01_interface.go
================================================
package defining_depenency_injection

import (
	"encoding/json"
	"errors"
)

// Saver persists the supplied bytes
type Saver interface {
	Save(data []byte) error
}

// SavePerson will validate and persist the supplied person
func SavePerson(person *Person, saver Saver) error {
	// validate the inputs
	err := person.validate()

	if err != nil {
		return err
	}

	// encode person to bytes
	bytes, err := person.encode()
	if err != nil {
		return err
	}

	// save the person and return the result
	return saver.Save(bytes)
}

// Person data object
type Person struct {
	Name  string
	Phone string
}

// validate the person object
func (p *Person) validate() error {
	if p.Name == "" {
		return errors.New("name missing")
	}

	if p.Phone == "" {
		return errors.New("phone missing")
	}

	return nil
}

// convert the person into bytes
func (p *Person) encode() ([]byte, error) {
	return json.Marshal(p)
}


================================================
FILE: ch01/01_defining_depenency_injection/02_function_literal.go
================================================
package defining_depenency_injection

import (
	"errors"
	"fmt"
)

// LoadPerson will load the requested person by ID.
// Errors include: invalid ID, missing person and failure to load or decode.
func LoadPerson(ID int, decodePerson func(data []byte) *Person) (*Person, error) {
	// validate the input
	if ID <= 0 {
		return nil, fmt.Errorf("invalid ID '%d' supplied", ID)
	}

	// load from storage
	bytes, err := loadPerson(ID)
	if err != nil {
		return nil, err
	}

	// decode bytes and return
	return decodePerson(bytes), nil
}

// load person as bytes from storage
func loadPerson(ID int) ([]byte, error) {
	// TODO: implement
	return nil, errors.New("not implemented")
}


================================================
FILE: ch01/01_defining_depenency_injection/03_test_without_nfs_test.go
================================================
package defining_depenency_injection

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func TestSavePerson_happyPath(t *testing.T) {
	// input
	in := &Person{
		Name:  "Sophia",
		Phone: "0123456789",
	}

	// mock the NFS
	mockNFS := &mockSaver{}
	mockNFS.On("Save", mock.Anything).Return(nil).Once()

	// Call Save
	resultErr := SavePerson(in, mockNFS)

	// validate result
	assert.NoError(t, resultErr)
	assert.True(t, mockNFS.AssertExpectations(t))
}

// mock implementation of Saver
type mockSaver struct {
	mock.Mock
}

// Save implements Saver
func (m *mockSaver) Save(data []byte) error {
	outputs := m.Mock.Called(data)

	return outputs.Error(0)
}


================================================
FILE: ch01/01_defining_depenency_injection/04_fail_test_without_nfs_test.go
================================================
package defining_depenency_injection

import (
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func TestSavePerson_nfsAlwaysFails(t *testing.T) {
	// input
	in := &Person{
		Name:  "Sophia",
		Phone: "0123456789",
	}

	// mock the NFS
	mockNFS := &mockSaver{}
	mockNFS.On("Save", mock.Anything).Return(errors.New("save failed")).Once()

	// Call Save
	resultErr := SavePerson(in, mockNFS)

	// validate result
	assert.Error(t, resultErr)
	assert.True(t, mockNFS.AssertExpectations(t))
}


================================================
FILE: ch01/02_code_smells/01_code_bloat/01_switch_type.go
================================================
package code_bloat

import (
	"strconv"
)

func AppendValue(buffer []byte, in interface{}) []byte {
	var value []byte

	// convert input to []byte
	switch concrete := in.(type) {
	case []byte:
		value = concrete

	case string:
		value = []byte(concrete)

	case int64:
		value = []byte(strconv.FormatInt(concrete, 10))

	case bool:
		value = []byte(strconv.FormatBool(concrete))

	case float64:
		value = []byte(strconv.FormatFloat(concrete, 'e', 3, 64))
	}

	buffer = append(buffer, value...)
	return buffer
}


================================================
FILE: ch01/02_code_smells/02_resistance_to_change/01_shotgun_surgey.go
================================================
package _2_resistance_to_change

import (
	"database/sql"
	"io"
)

// Renderer will render a person to the supplied writer
type Renderer struct{}

func (r Renderer) render(name, phone string, output io.Writer) {
	// output the person
}

// Validator will validate the supplied person has all the required fields
type Validator struct{}

func (v Validator) validate(name, phone string) error {
	// validate the person
	return nil
}

// Saver will save the supplied person to the DB
type Saver struct{}

func (s *Saver) Save(db *sql.DB, name, phone string) {
	// save the person to db
}


================================================
FILE: ch01/02_code_smells/03_wasted_effort/01_excessive_comments.go
================================================
package wasted_effort

// Excessive comments
func outputOrderedPeopleA(in []*Person) {
	// This code orders people by name.
	// In cases where the name is the same, it will order by phone number.
	// The sort algorithm used is a bubble sort
	// WARNING: this sort will change the items of the input array
	for range in {
		// ... sort code removed ...
	}

	outputPeople(in)
}

// Comments replaced with descriptive names
func outputOrderedPeopleB(in []*Person) {
	sortPeople(in)
	outputPeople(in)
}

func outputPeople(in []*Person) {
	// TODO: implement
}

// any special instructions that MUST be documented relating to the sort should go here
func sortPeople(in []*Person) {
	// TODO: implement
}

// Person data object
type Person struct {
	Name  string
	Phone string
}


================================================
FILE: ch01/02_code_smells/03_wasted_effort/02_complicated_go.go
================================================
package wasted_effort

import (
	"image"
	"image/color"
	"math"
)

func d(r, v float64, i *image.RGBA, c color.Color) {
	for a := float64(0); a < 360; a++ {
		ra := math.Pi * 2 * a / 360
		x := r*math.Sin(ra) + v
		y := r*math.Cos(ra) + v
		i.Set(int(x), int(y), c)
	}
}


================================================
FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config/config.go
================================================
// +build bad

package config

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment"
)

// Config defines the JSON format of the config file
type Config struct {
	// Address is the host and port to bind to.
	// Default 0.0.0.0:8080
	Address string

	// DefaultCurrency is the default currency of the system
	DefaultCurrency payment.Currency
}

// Load will load the JSON config from the file supplied
func Load(filename string) (*Config, error) {
	// TODO: load currency from file
	return nil, errors.New("not implemented yet")
}


================================================
FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment/currency.go
================================================
// +build bad

package payment

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config"
)

// Currency is custom type for currency
type Currency string

// Processor processes payments
type Processor struct {
	Config *config.Config
}

// Pay makes a payment in the default currency
func (p *Processor) Pay(amount float64) error {
	// TODO: implement me
	return errors.New("not implemented yet")
}


================================================
FILE: ch01/02_code_smells/04_tight_coupling/02_object_orgy.go
================================================
package _4_tight_coupling

import (
	"errors"
	"io/ioutil"
	"net/http"
)

type PageLoader struct {
}

func (o *PageLoader) LoadPage(url string) ([]byte, error) {
	b := newFetcher()

	// check cache
	payload, err := b.cache.Get(url)
	if err == nil {
		// found in cache
		return payload, nil
	}

	// call upstream
	resp, err := b.httpClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// extract data from HTTP response
	payload, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	// save to cache asynchronously
	go func(key string, value []byte) {
		b.cache.Set(key, value)
	}(url, payload)

	// return
	return payload, nil
}

type Fetcher struct {
	httpClient http.Client
	cache      *Cache
}

func newFetcher() *Fetcher {
	return &Fetcher{}
}

type Cache struct {
	// not implemented
}

func (c *Cache) Get(key string) ([]byte, error) {
	// not implemented
	return nil, errors.New("not implemented")
}

func (c *Cache) Set(key string, data []byte) error {
	// not implemented
	return errors.New("not implemented")
}


================================================
FILE: ch01/02_code_smells/04_tight_coupling/03_feature_envy.go
================================================
package _4_tight_coupling

import (
	"errors"
	"time"
)

type searchRequest struct {
	query string
	start time.Time
	end   time.Time
}

func (request searchRequest) validate() error {
	if request.query == "" {
		return errors.New("search term is missing")
	}
	if request.start.IsZero() || request.start.After(time.Now()) {
		return errors.New("start time is missing or invalid")
	}
	if request.end.IsZero() || request.end.Before(request.start) {
		return errors.New("end time is missing or invalid")
	}

	return nil
}

type searchResults struct {
	result string
}

func doSearchWithEnvy(request searchRequest) ([]searchResults, error) {
	// validate request
	if request.query == "" {
		return nil, errors.New("search term is missing")
	}
	if request.start.IsZero() || request.start.After(time.Now()) {
		return nil, errors.New("start time is missing or invalid")
	}
	if request.end.IsZero() || request.end.Before(request.start) {
		return nil, errors.New("end time is missing or invalid")
	}

	return performSearch(request)
}

func doSearchWithoutEnvy(request searchRequest) ([]searchResults, error) {
	err := request.validate()
	if err != nil {
		return nil, err
	}

	return performSearch(request)
}

func performSearch(request searchRequest) ([]searchResults, error) {
	// TODO: implement
	return nil, errors.New("not implemented")
}


================================================
FILE: ch02/01_single_responsibility_principle/01_responsibility_vs_change.go
================================================
package srp

import (
	"fmt"
	"io"
)

// CalculatorV1 calculates the test coverage for a directory and it's sub-directories
type CalculatorV1 struct {
	// coverage data populated by `Calculate()` method
	data map[string]float64
}

// Calculate will calculate the coverage
func (c *CalculatorV1) Calculate(path string) error {
	// run `go test -cover ./[path]/...` and store the results
	return nil
}

// Output will print the coverage data to the supplied writer
func (c *CalculatorV1) Output(writer io.Writer) {
	for path, result := range c.data {
		fmt.Fprintf(writer, "%s -> %.1f\n", path, result)
	}
}


================================================
FILE: ch02/01_single_responsibility_principle/02_responsibility_vs_change.go
================================================
package srp

import (
	"fmt"
	"io"
)

// CalculatorV2 calculates the test coverage for a directory and it's sub-directories
type CalculatorV2 struct {
	// coverage data populated by `Calculate()` method
	data map[string]float64
}

// Calculate will calculate the coverage
func (c *CalculatorV2) Calculate(path string) error {
	// run `go test -cover ./[path]/...` and store the results
	return nil
}

// Output will print the coverage data to the supplied writer
func (c CalculatorV2) Output(writer io.Writer) {
	for path, result := range c.data {
		fmt.Fprintf(writer, "%s -> %.1f\n", path, result)
	}
}

// OutputCSV will print the coverage data to the supplied writer
func (c CalculatorV2) OutputCSV(writer io.Writer) {
	for path, result := range c.data {
		fmt.Fprintf(writer, "%s,%.1f\n", path, result)
	}
}


================================================
FILE: ch02/01_single_responsibility_principle/03_responsibility_vs_change.go
================================================
package srp

import (
	"fmt"
	"io"
)

// CalculatorV3 calculates the test coverage for a directory and it's sub-directories
type CalculatorV3 struct {
	// coverage data populated by `Calculate()` method
	data map[string]float64
}

// Calculate will calculate the coverage
func (c *CalculatorV3) Calculate(path string) error {
	// run `go test -cover ./[path]/...` and store the results
	return nil
}

func (c *CalculatorV3) getData() map[string]float64 {
	// copy and return the map
	return nil
}

type Printer interface {
	Output(data map[string]float64)
}

type DefaultPrinter struct {
	Writer io.Writer
}

// Output implements Printer
func (d *DefaultPrinter) Output(data map[string]float64) {
	for path, result := range data {
		fmt.Fprintf(d.Writer, "%s -> %.1f\n", path, result)
	}
}

type CSVPrinter struct {
	Writer io.Writer
}

// Output implements Printer
func (d *CSVPrinter) Output(data map[string]float64) {
	for path, result := range data {
		fmt.Fprintf(d.Writer, "%s,%.1f\n", path, result)
	}
}


================================================
FILE: ch02/01_single_responsibility_principle/04_long_method.go
================================================
package srp

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"strconv"
)

func loadUserHandlerLong(resp http.ResponseWriter, req *http.Request) {
	err := req.ParseForm()
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
	userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}

	row := DB.QueryRow("SELECT * FROM Users WHERE ID = ?", userID)

	person := &Person{}
	err = row.Scan(&person.ID, &person.Name, &person.Phone)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}

	encoder := json.NewEncoder(resp)
	err = encoder.Encode(person)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
}

var DB *sql.DB

type Person struct {
	ID    int64
	Name  string
	Phone string
}


================================================
FILE: ch02/01_single_responsibility_principle/04_long_method_test.go
================================================
package srp

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
)

func TestLoadUserHandler(t *testing.T) {
	// build request
	req := &http.Request{
		Form: url.Values{},
	}
	req.Form.Add("UserID", "1234")

	// call function under test
	resp := httptest.NewRecorder()
	loadUserHandlerLong(resp, req)

	// validate result
	assert.Equal(t, http.StatusOK, resp.Code)

	expectedBody := `{"ID":1,"Name":"Bob","Phone":"0123456789"}` + "\n"
	assert.Equal(t, expectedBody, resp.Body.String())
}

func TestMain(m *testing.M) {
	// create fake DB for this test
	var mock sqlmock.Sqlmock
	DB, mock, _ = sqlmock.New()

	// config fake response
	mock.ExpectQuery(".*").WillReturnRows(
		sqlmock.NewRows([]string{"ID", "Name", "Phone"}).AddRow(
			1, "Bob", "0123456789"))

	os.Exit(m.Run())
}


================================================
FILE: ch02/01_single_responsibility_principle/05_srp_method.go
================================================
package srp

import (
	"encoding/json"
	"net/http"
	"strconv"
)

func loadUserHandlerSRP(resp http.ResponseWriter, req *http.Request) {
	userID, err := extractIDFromRequest(req)
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}

	person, err := loadPersonByID(userID)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}

	outputPerson(resp, person)
}

func extractIDFromRequest(req *http.Request) (int64, error) {
	err := req.ParseForm()
	if err != nil {
		return 0, err
	}
	return strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
}

func loadPersonByID(userID int64) (*Person, error) {
	row := DB.QueryRow("SELECT * FROM Users WHERE userID = ?", userID)

	person := &Person{}
	err := row.Scan(person.ID, person.Name, person.Phone)
	if err != nil {
		return nil, err
	}
	return person, nil
}

func outputPerson(resp http.ResponseWriter, person *Person) {
	encoder := json.NewEncoder(resp)
	err := encoder.Encode(person)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
}


================================================
FILE: ch02/02_open_closed_principle/01_open_closed_failure.go
================================================
package ocp

import (
	"io"
	"net/http"
)

func BuildOutputOCPFail(response http.ResponseWriter, format string, person Person) {
	var err error

	switch format {
	case "csv":
		err = outputCSV(response, person)

	case "json":
		err = outputJSON(response, person)
	}

	if err != nil {
		// output a server error and quit
		response.WriteHeader(http.StatusInternalServerError)
		return
	}

	response.WriteHeader(http.StatusOK)
}

// output the person as CSV and return error when failing to do so
func outputCSV(writer io.Writer, person Person) error {
	// TODO: implement
	return nil
}

// output the person as JSON and return error when failing to do so
func outputJSON(writer io.Writer, person Person) error {
	// TODO: implement
	return nil
}

// A data transfer object that represents a person
type Person struct {
	Name  string
	Email string
}


================================================
FILE: ch02/02_open_closed_principle/02_open_closed_success.go
================================================
package ocp

import (
	"io"
	"net/http"
)

func BuildOutputOCPSuccess(response http.ResponseWriter, formatter PersonFormatter, person Person) {
	err := formatter.Format(response, person)
	if err != nil {
		// output a server error and quit
		response.WriteHeader(http.StatusInternalServerError)
		return
	}

	response.WriteHeader(http.StatusOK)
}

type PersonFormatter interface {
	Format(writer io.Writer, person Person) error
}

// output the person as CSV
type CSVPersonFormatter struct{}

// Format implements the PersonFormatter interface
func (c *CSVPersonFormatter) Format(writer io.Writer, person Person) error {
	// TODO: implement
	return nil
}

// output the person as JSON
type JSONPersonFormatter struct{}

// Format implements the PersonFormatter interface
func (j *JSONPersonFormatter) Format(writer io.Writer, person Person) error {
	// TODO: implement
	return nil
}


================================================
FILE: ch02/02_open_closed_principle/03_shotgun_surgery.go
================================================
package ocp

import (
	"net/http"
	"strconv"
)

func GetUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
	// validate inputs
	err := req.ParseForm()
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
	userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}

	user := loadUser(userID)
	outputUser(resp, user)
}

func DeleteUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
	// validate inputs
	err := req.ParseForm()
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
	userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}

	deleteUser(userID)
}

func loadUser(userID int64) interface{} {
	// TODO: implement
	return nil
}

func deleteUser(userID int64) {
	// TODO: implement
}

func outputUser(resp http.ResponseWriter, user interface{}) {
	// TODO: implement
}


================================================
FILE: ch02/02_open_closed_principle/04_after_shotgun_surgery.go
================================================
package ocp

import (
	"errors"
	"net/http"
	"net/url"
	"strconv"
)

func GetUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
	// validate inputs
	err := req.ParseForm()
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
	userID, err := extractUserID(req.Form)
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}

	user := loadUser(userID)
	outputUser(resp, user)
}

func DeleteUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
	// validate inputs
	err := req.ParseForm()
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
	userID, err := extractUserID(req.Form)
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}

	deleteUser(userID)
}

func extractUserID(values url.Values) (int64, error) {
	userID, err := strconv.ParseInt(values.Get("UserID"), 10, 64)
	if err != nil {
		return 0, err
	}

	if userID <= 0 {
		return 0, errors.New("userID must be positive")
	}

	return userID, nil
}


================================================
FILE: ch02/02_open_closed_principle/05_composition.go
================================================
package ocp

import (
	"database/sql"
)

type rowConverter struct {
}

// populate the supplied Person from *sql.Row or *sql.Rows object
func (d *rowConverter) populate(in *Person, scan func(dest ...interface{}) error) error {
	return scan(in.Name, in.Email)
}

type LoadPerson struct {
	// compose the row converter into this loader
	rowConverter
}

func (loader *LoadPerson) ByID(id int) (Person, error) {
	row := loader.loadFromDB(id)

	person := Person{}
	// call the composed "abstract class"
	err := loader.populate(&person, row.Scan)

	return person, err
}

func (loader *LoadPerson) loadFromDB(id int) *sql.Row {
	// TODO: implement
	return nil
}

type LoadAll struct {
	// compose the row converter into this loader
	rowConverter
}

func (loader *LoadPerson) All() ([]Person, error) {
	rows := loader.loadAllFromDB()
	defer rows.Close()

	var output []Person
	for rows.Next() {
		person := Person{}

		// call the composed "abstract class"
		err := loader.populate(&person, rows.Scan)
		if err != nil {
			return nil, err
		}
	}

	return output, nil
}

func (loader *LoadPerson) loadAllFromDB() *sql.Rows {
	// TODO: implement
	return nil
}


================================================
FILE: ch02/02_open_closed_principle/06_handler_struct.go
================================================
package ocp

import (
	"net/http"
)

// a HTTP health check handler in long form
type healthCheckLong struct {
}

func (h *healthCheckLong) ServeHTTP(resp http.ResponseWriter, _ *http.Request) {
	resp.WriteHeader(http.StatusNoContent)
}

func healthCheckLongUsage() {
	http.Handle("/health", &healthCheckLong{})
}


================================================
FILE: ch02/02_open_closed_principle/07_handler_func.go
================================================
package ocp

import (
	"net/http"
)

// a HTTP health check handler in short form
func healthCheckShort(resp http.ResponseWriter, _ *http.Request) {
	resp.WriteHeader(http.StatusNoContent)
}

func healthCheckShortUsage() {
	http.Handle("/health", http.HandlerFunc(healthCheckShort))
}


================================================
FILE: ch02/03_liskov_substitution_principle/01_violation/example.go
================================================
package lsp_violation

func Go(vehicle actions) {
	if sled, ok := vehicle.(*Sled); ok {
		sled.pushStart()
	} else {
		vehicle.startEngine()
	}

	vehicle.drive()
}

type actions interface {
	drive()
	startEngine()
}

type Vehicle struct {
}

func (v Vehicle) drive() {
	// TODO: implement
}

func (v Vehicle) startEngine() {
	// TODO: implement
}

func (v Vehicle) stopEngine() {
	// TODO: implement
}

type Car struct {
	Vehicle
}

type Sled struct {
	Vehicle
}

func (s Sled) startEngine() {
	// override so that is does nothing
}

func (s Sled) stopEngine() {
	// override so that is does nothing
}

func (s Sled) pushStart() {
	// TODO: implement
}


================================================
FILE: ch02/03_liskov_substitution_principle/02_fixed/example.go
================================================
package fixedv1

func Go(vehicle actions) {
	switch concrete := vehicle.(type) {
	case poweredActions:
		concrete.startEngine()

	case unpoweredActions:
		concrete.pushStart()
	}

	vehicle.drive()
}

type actions interface {
	drive()
}

type poweredActions interface {
	actions
	startEngine()
	stopEngine()
}

type unpoweredActions interface {
	actions
	pushStart()
}

type Vehicle struct {
}

func (v Vehicle) drive() {
	// TODO: implement
}

type PoweredVehicle struct {
	Vehicle
}

func (v PoweredVehicle) startEngine() {
	// common engine start code
}

type Car struct {
	PoweredVehicle
}

type Sled struct {
	Vehicle
}

func (s Sled) pushStart() {
	// do nothing
}


================================================
FILE: ch02/03_liskov_substitution_principle/03_fixed/example.go
================================================
package fixedv2

func Go(vehicle actions) {
	vehicle.start()
	vehicle.drive()
}

type actions interface {
	start()
	drive()
}

type Car struct {
	poweredVehicle
}

func (c Car) start() {
	c.poweredVehicle.startEngine()
}

func (c Car) drive() {
	// TODO: implement
}

type poweredVehicle struct {
}

func (p poweredVehicle) startEngine() {
	// common engine start code
}

type Sled struct {
}

func (s Sled) start() {
	// push start
}

func (s Sled) drive() {
	// TODO: implement
}


================================================
FILE: ch02/03_liskov_substitution_principle/04_behaviour.go
================================================
package lsp

type Collection interface {
	Add(item interface{})
	Get(index int) interface{}
}

type CollectionImpl struct {
	items []interface{}
}

func (c *CollectionImpl) Add(item interface{}) {
	c.items = append(c.items, item)
}

func (c *CollectionImpl) Get(index int) interface{} {
	return c.items[index]
}

type ReadOnlyCollection struct {
	CollectionImpl
}

func (ro *ReadOnlyCollection) Add(item interface{}) {
	// intentionally does nothing
}


================================================
FILE: ch02/03_liskov_substitution_principle/05_behaviour_fixed.go
================================================
package lsp

type ImmutableCollection interface {
	Get(index int) interface{}
}

type MutableCollection interface {
	ImmutableCollection
	Add(item interface{})
}

type ReadOnlyCollectionV2 struct {
	items []interface{}
}

func (ro *ReadOnlyCollectionV2) Get(index int) interface{} {
	return ro.items[index]
}

type CollectionImplV2 struct {
	ReadOnlyCollectionV2
}

func (c *CollectionImplV2) Add(item interface{}) {
	c.items = append(c.items, item)
}


================================================
FILE: ch02/04_interface_segregation_principle/01_fat_interface.go
================================================
package isp

import (
	"context"
)

type Item struct {
	Key     string
	Payload []byte
}

type FatDbInterface interface {
	BatchGetItem(IDs ...int) ([]Item, error)
	BatchGetItemWithContext(ctx context.Context, IDs ...int) ([]Item, error)

	BatchPutItem(items ...Item) error
	BatchPutItemWithContext(ctx context.Context, items ...Item) error

	DeleteItem(ID int) error
	DeleteItemWithContext(ctx context.Context, item Item) error

	GetItem(ID int) (Item, error)
	GetItemWithContext(ctx context.Context, ID int) (Item, error)

	PutItem(item Item) error
	PutItemWithContext(ctx context.Context, item Item) error

	Query(query string, args ...interface{}) ([]Item, error)
	QueryWithContext(ctx context.Context, query string, args ...interface{}) ([]Item, error)

	UpdateItem(item Item) error
	UpdateItemWithContext(ctx context.Context, item Item) error
}

type Cache struct {
	db FatDbInterface
}

func (c *Cache) Get(key string) interface{} {
	// code removed

	// load from DB
	_, _ = c.db.GetItem(42)

	// code removed
	return nil
}

func (c *Cache) Set(key string, value interface{}) {
	// code removed

	// save to DB
	_ = c.db.PutItem(Item{})

	// code removed
}


================================================
FILE: ch02/04_interface_segregation_principle/02_thin_interface.go
================================================
package isp

type myDB interface {
	GetItem(ID int) (Item, error)
	PutItem(item Item) error
}

type CacheV2 struct {
	db myDB
}

func (c *CacheV2) Get(key string) interface{} {
	// code removed

	// load from DB
	_, _ = c.db.GetItem(42)

	// code removed
	return nil
}

func (c *CacheV2) Set(key string, value interface{}) {
	// code removed

	// save from DB
	_ = c.db.PutItem(Item{})

	// code removed
}


================================================
FILE: ch02/04_interface_segregation_principle/03_repeated_inputs.go
================================================
package isp

import (
	"context"
	"errors"
)

func Encrypt(ctx context.Context, data []byte) ([]byte, error) {
	// As this operation make take too long, we need to be able to kill it
	stop := ctx.Done()
	result := make(chan []byte, 1)

	go func() {
		defer close(result)

		// pull the encryption key from context
		keyRaw := ctx.Value("encryption-key")
		if keyRaw == nil {
			panic("encryption key not found in context")
		}
		key := keyRaw.([]byte)

		// perform encryption
		ciperText := performEncryption(key, data)

		// signal complete by sending the result
		result <- ciperText
	}()

	select {
	case ciperText := <-result:
		// happy path
		return ciperText, nil

	case <-stop:
		// cancelled
		return nil, errors.New("operation cancelled")
	}
}

func performEncryption(key []byte, data []byte) []byte {
	// TODO: implement
	return nil
}


================================================
FILE: ch02/04_interface_segregation_principle/04_repeated_inputs.go
================================================
package isp

import (
	"errors"
)

type Value interface {
	Value(key interface{}) interface{}
}

type Monitor interface {
	Done() <-chan struct{}
}

func EncryptV2(keyValue Value, monitor Monitor, data []byte) ([]byte, error) {
	// As this operation make take too long, we need to be able to kill it
	stop := monitor.Done()
	result := make(chan []byte, 1)

	go func() {
		defer close(result)

		// pull the encryption key from Value
		keyRaw := keyValue.Value("encryption-key")
		if keyRaw == nil {
			panic("encryption key not found in context")
		}
		key := keyRaw.([]byte)

		// perform encryption
		ciperText := performEncryption(key, data)

		// signal complete by sending the result
		result <- ciperText
	}()

	select {
	case ciperText := <-result:
		// happy path
		return ciperText, nil

	case <-stop:
		// cancelled
		return nil, errors.New("operation cancelled")
	}
}


================================================
FILE: ch02/04_interface_segregation_principle/05_repeated_inputs.go
================================================
package isp

import (
	"context"
)

func UseEncryptV2() {
	// create a context
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// store the key
	ctx = context.WithValue(ctx, "encryption-key", "-secret-")

	// call the function
	_, _ = EncryptV2(ctx, ctx, []byte("my data"))
}


================================================
FILE: ch02/04_interface_segregation_principle/06_implicit_interfaces.go
================================================
package isp

import (
	"fmt"
)

type Talker interface {
	SayHello() string
}

type Dog struct{}

// The method implicitly implements the Talker interface
func (d Dog) SayHello() string {
	return "Woof!"
}

func Speak() {
	var talker Talker
	talker = Dog{}

	fmt.Print(talker.SayHello())
}


================================================
FILE: ch03/01_optimizing_for_humans/01_not_so_simple.go
================================================
package humans

import (
	"bytes"
	"strconv"
	"strings"
)

func NotSoSimple(ID int64, name string, age int, registered bool) string {
	out := &bytes.Buffer{}
	out.WriteString(strconv.FormatInt(ID, 10))
	out.WriteString("-")
	out.WriteString(strings.Replace(name, " ", "_", -1))
	out.WriteString("-")
	out.WriteString(strconv.Itoa(age))
	out.WriteString("-")
	out.WriteString(strconv.FormatBool(registered))
	return out.String()
}


================================================
FILE: ch03/01_optimizing_for_humans/02_start_simple.go
================================================
package humans

import (
	"fmt"
	"strings"
)

func Simpler(ID int64, name string, age int, registered bool) string {
	nameWithNoSpaces := strings.Replace(name, " ", "_", -1)
	return fmt.Sprintf("%d-%s-%d-%t", ID, nameWithNoSpaces, age, registered)
}


================================================
FILE: ch03/01_optimizing_for_humans/03_too_abstract.go
================================================
package humans

import (
	"io/ioutil"
	"net/http"
)

type myGetter interface {
	Get(url string) (*http.Response, error)
}

func TooAbstract(getter myGetter, url string) ([]byte, error) {
	resp, err := getter.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)
}


================================================
FILE: ch03/01_optimizing_for_humans/04_common_concept.go
================================================
package humans

import (
	"io/ioutil"
	"net/http"
)

func CommonConcept(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)
}


================================================
FILE: ch03/01_optimizing_for_humans/05_boolean_param.go
================================================
package humans

import (
	"time"
)

type Pet struct {
	Name string
	Dog  bool
	Born time.Time
}

func NewPet(name string, isDog bool) Pet {
	return Pet{
		Name: name,
		Dog:  isDog,
		Born: time.Now(),
	}
}

func CreatePetsV1() {
	NewPet("Fido", true)
}


================================================
FILE: ch03/01_optimizing_for_humans/06_hidden_boolean.go
================================================
package humans

const (
	isDog = true
	isCat = false
)

func NewDog(name string) Pet {
	return NewPet(name, isDog)
}

func NewCat(name string) Pet {
	return NewPet(name, isCat)
}

func CreatePetsV2() {
	NewDog("Fido")
}


================================================
FILE: ch03/01_optimizing_for_humans/07_wide_formatter.go
================================================
package humans

type WideFormatter interface {
	ToCSV(pets []Pet) ([]byte, error)
	ToGOB(pets []Pet) ([]byte, error)
	ToJSON(pets []Pet) ([]byte, error)
}


================================================
FILE: ch03/01_optimizing_for_humans/08_thin_formatters.go
================================================
package humans

type ThinFormatter interface {
	Format(pets []Pet) ([]byte, error)
}

type CSVFormatter struct{}

func (f CSVFormatter) Format(pets []Pet) ([]byte, error) {
	// convert slice of pets to CSV
	return nil, nil
}

type GOBFormatter struct{}

func (f GOBFormatter) Format(pets []Pet) ([]byte, error) {
	// convert slice of pets to GOB
	return nil, nil
}

type JSONFormatter struct{}

func (f JSONFormatter) Format(pets []Pet) ([]byte, error) {
	// convert slice of pets to JSON
	return nil, nil
}


================================================
FILE: ch03/01_optimizing_for_humans/09_extra_config.go
================================================
package humans

// PetFetcher searches the data store for pets whos name matches the search string.
// Limit is optional (default is 100).  Offset is optional (default 0).
// sortBy is optional (default name).  sortAscending is optional
func PetFetcher(search string, limit int, offset int, sortBy string, sortAscending bool) []Pet {
	return []Pet{}
}

func PetFetcherTypicalUsage() {
	_ = PetFetcher("Fido", 0, 0, "", true)
}


================================================
FILE: ch03/02_unit_tests/01_loader.go
================================================
package unit_tests

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"testing"

	"github.com/stretchr/testify/assert"
)

type Loader interface {
	Load(ID int) (*Pet, error)
}

func TestLoadAndPrint_happyPath(t *testing.T) {
	result := &bytes.Buffer{}
	LoadAndPrint(&happyPathLoader{}, 1, result)
	assert.Contains(t, result.String(), "Pet named")
}

func TestLoadAndPrint_notFound(t *testing.T) {
	result := &bytes.Buffer{}
	LoadAndPrint(&missingLoader{}, 1, result)
	assert.Contains(t, result.String(), "no such pet")
}

func TestLoadAndPrint_error(t *testing.T) {
	result := &bytes.Buffer{}
	LoadAndPrint(&errorLoader{}, 1, result)
	assert.Contains(t, result.String(), "failed to load")
}

func LoadAndPrint(loader Loader, ID int, dest io.Writer) {
	loadedPet, err := loader.Load(ID)
	if err != nil {
		fmt.Fprintf(dest, "failed to load pet with ID %d with error: %s", ID, err)
		return
	}

	if loadedPet == nil {
		fmt.Fprintf(dest, "no such pet found")
		return
	}

	fmt.Fprintf(dest, "Pet named %s loaded", loadedPet.Name)
}

// implements Loader
type happyPathLoader struct {
}

func (l *happyPathLoader) Load(ID int) (*Pet, error) {
	return &Pet{}, nil
}

// implements Loader
type missingLoader struct {
}

func (l *missingLoader) Load(ID int) (*Pet, error) {
	return nil, nil
}

// implements Loader
type errorLoader struct {
}

func (l *errorLoader) Load(ID int) (*Pet, error) {
	return nil, errors.New("failed")
}


================================================
FILE: ch03/02_unit_tests/02_language_feature.go
================================================
package unit_tests

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

type Pet struct {
	Name string
}

func NewPet(name string) *Pet {
	return &Pet{
		Name: name,
	}
}

func TestLanguageFeatures(t *testing.T) {
	petFish := NewPet("Goldie")
	assert.IsType(t, &Pet{}, petFish)
}


================================================
FILE: ch03/02_unit_tests/03_simple_test.go
================================================
package unit_tests

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func concat(a, b string) string {
	return a + b
}

func TestTooSimple(t *testing.T) {
	a := "Hello "
	b := "World"
	expected := "Hello World"

	assert.Equal(t, expected, concat(a, b))
}


================================================
FILE: ch03/02_unit_tests/04_test_from_api.go
================================================
package unit_tests

import (
	"database/sql"
)

type PetSaver struct{}

// save the supplied pet and return the ID
func (p PetSaver) Save(pet Pet) (int, error) {
	err := p.validate(pet)
	if err != nil {
		return 0, err
	}

	result, err := p.save(pet)
	if err != nil {
		return 0, err
	}

	return p.extractID(result)
}

// ensure the pet record is complete
func (p PetSaver) validate(pet Pet) error {
	return nil
}

// save to the datastore
func (p PetSaver) save(pet Pet) (sql.Result, error) {
	return nil, nil
}

// extract the ID from the result
func (p PetSaver) extractID(result sql.Result) (int, error) {
	return 0, nil
}


================================================
FILE: ch03/02_unit_tests/05_repeated_code.go
================================================
package unit_tests

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

// Round the supplied number to the nearest integer
func Round(in float64) int {
	return 0
}

func TestRound_down(t *testing.T) {
	in := float64(1.1)
	expected := 1

	result := Round(in)
	assert.Equal(t, expected, result)
}

func TestRound_up(t *testing.T) {
	in := float64(3.7)
	expected := 4

	result := Round(in)
	assert.Equal(t, expected, result)
}

func TestRound_noChange(t *testing.T) {
	in := float64(6.0)
	expected := 6

	result := Round(in)
	assert.Equal(t, expected, result)
}


================================================
FILE: ch03/02_unit_tests/06_tdt.go
================================================
package unit_tests

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestRound(t *testing.T) {
	scenarios := []struct {
		desc     string
		in       float64
		expected int
	}{
		{
			desc:     "round down",
			in:       1.1,
			expected: 1,
		},
		{
			desc:     "round up",
			in:       3.7,
			expected: 4,
		},
		{
			desc:     "unchanged",
			in:       6.0,
			expected: 6,
		},
	}

	for _, scenario := range scenarios {
		in := scenario.in

		result := Round(in)
		assert.Equal(t, scenario.expected, result)
	}
}


================================================
FILE: ch03/02_unit_tests/07_person_loader.go
================================================
package unit_tests

import (
	"errors"
)

var ErrNotFound = errors.New("person not found")

type Person struct {
	Name string
}

//go:generate mockery -name PersonLoader -testonly -inpkg -case=underscore
type PersonLoader interface {
	Load(ID int) (*Person, error)
}

func LoadPersonName(loader PersonLoader, ID int) (string, error) {
	person, err := loader.Load(ID)
	if err != nil {
		return "", err
	}

	return person.Name, nil
}


================================================
FILE: ch03/02_unit_tests/08_stub.go
================================================
package unit_tests

// Stubbed implementation of PersonLoader
type PersonLoaderStub struct {
	Person *Person
	Error  error
}

func (p *PersonLoaderStub) Load(ID int) (*Person, error) {
	return p.Person, p.Error
}


================================================
FILE: ch03/02_unit_tests/09_stub_tdt.go
================================================
package unit_tests

import (
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestLoadPersonNameStubs(t *testing.T) {
	// this value does not matter as the stub ignores it
	fakeID := 1

	scenarios := []struct {
		desc         string
		loaderStub   *PersonLoaderStub
		expectedName string
		expectErr    bool
	}{
		{
			desc: "happy path",
			loaderStub: &PersonLoaderStub{
				Person: &Person{Name: "Sophia"},
			},
			expectedName: "Sophia",
			expectErr:    false,
		},
		{
			desc: "input error",
			loaderStub: &PersonLoaderStub{
				Error: ErrNotFound,
			},
			expectedName: "",
			expectErr:    true,
		},
		{
			desc: "system error path",
			loaderStub: &PersonLoaderStub{
				Error: errors.New("something failed"),
			},
			expectedName: "",
			expectErr:    true,
		},
	}

	for _, scenario := range scenarios {
		result, resultErr := LoadPersonName(scenario.loaderStub, fakeID)

		assert.Equal(t, scenario.expectedName, result, scenario.desc)
		assert.Equal(t, scenario.expectErr, resultErr != nil, scenario.desc)
	}
}


================================================
FILE: ch03/02_unit_tests/10_mocks.go
================================================
package unit_tests

import (
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func TestLoadPersonName(t *testing.T) {
	// this value does not matter as the stub ignores it
	fakeID := 1

	scenarios := []struct {
		desc          string
		configureMock func(stub *PersonLoaderMock)
		expectedName  string
		expectErr     bool
	}{
		{
			desc: "happy path",
			configureMock: func(loaderMock *PersonLoaderMock) {
				loaderMock.On("Load", mock.Anything).
					Return(&Person{Name: "Sophia"}, nil).
					Once()
			},
			expectedName: "Sophia",
			expectErr:    false,
		},
		{
			desc: "input error",
			configureMock: func(loaderMock *PersonLoaderMock) {
				loaderMock.On("Load", mock.Anything).
					Return(nil, ErrNotFound).
					Once()
			},
			expectedName: "",
			expectErr:    true,
		},
		{
			desc: "system error path",
			configureMock: func(loaderMock *PersonLoaderMock) {
				loaderMock.On("Load", mock.Anything).
					Return(nil, errors.New("something failed")).
					Once()
			},
			expectedName: "",
			expectErr:    true,
		},
	}

	for _, scenario := range scenarios {
		mockLoader := &PersonLoaderMock{}
		scenario.configureMock(mockLoader)

		result, resultErr := LoadPersonName(mockLoader, fakeID)

		assert.Equal(t, scenario.expectedName, result, scenario.desc)
		assert.Equal(t, scenario.expectErr, resultErr != nil, scenario.desc)
		assert.True(t, mockLoader.AssertExpectations(t), scenario.desc)
	}
}

// Mocked implementation of PersonLoader
type PersonLoaderMock struct {
	mock.Mock
}

func (p *PersonLoaderMock) Load(ID int) (*Person, error) {
	outputs := p.Mock.Called(ID)

	person := outputs.Get(0)
	err := outputs.Error(1)

	if person != nil {
		return person.(*Person), err
	}

	return nil, err
}


================================================
FILE: ch03/03_test_induced_damage/01_io_closer.go
================================================
package test_damage

import (
	"io"
)

func WriteAndClose(destination io.WriteCloser, contents string) error {
	defer destination.Close()

	_, err := destination.Write([]byte(contents))
	if err != nil {
		return err
	}

	return nil
}


================================================
FILE: ch03/03_test_induced_damage/02_json.go
================================================
package test_damage

import (
	"encoding/json"
	"io"
)

func PrintAsJSON(destination io.Writer, plant Plant) error {
	bytes, err := json.Marshal(plant)
	if err != nil {
		return err
	}

	destination.Write(bytes)
	return nil
}

type Plant struct {
	Name string
}


================================================
FILE: ch03/04_visualizing_dependencies/depgraph.sh
================================================
#!/usr/bin/env bash

# Note:
# This script should be run in the base directory of the project/service

# Inputs
#
# This cuts down on typing by allowing you to enter only the sub-directory you wish to graph; instead of the entire
# package
prefix="./"
PKG=${1#$prefix}

# Constants
#
# Save the file on the desktop (so it's easy to find)
DEST_FILE=~/Desktop/depgraph.png

# Calculate the package in the current directory and assume this is the base or project package
BASE_PKG=$(go list)
EXCLUSIONS="$BASE_PKG/vendor"
BASE_PKG_DELIMITED=$(echo $BASE_PKG | sed 's/\//\\\//g')

# Generate
godepgraph -s \
        -o "$BASE_PKG" \
        -p "$EXCLUSIONS" \
        $BASE_PKG/${PKG} |
        sed "s/$BASE_PKG_DELIMITED//g" | dot -Tpng -o $DEST_FILE

# Open
open $DEST_FILE


================================================
FILE: ch03/fake.go
================================================
package Hands_On_Dependency_Injection_in_Go

func init() {
	// This file is included so that Go tools (like `go list`) will find Go code in this directory and not error
}


================================================
FILE: ch04/01_welcome/01_bad_names.go
================================================
package welcome

type HouseV1 struct {
	a string
	b int
	t int
	p float64
}


================================================
FILE: ch04/01_welcome/02_improved_names.go
================================================
package welcome

type HouseV2 struct {
	address  string
	bedrooms int
	toilets  int
	price    float64
}


================================================
FILE: ch04/01_welcome/03_long_method.go
================================================
package welcome

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"strconv"
)

func longMethod(resp http.ResponseWriter, req *http.Request) {
	err := req.ParseForm()
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}
	userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
	if err != nil {
		resp.WriteHeader(http.StatusPreconditionFailed)
		return
	}

	row := DB.QueryRow("SELECT * FROM people WHERE ID = ?", userID)

	person := &Person{}
	err = row.Scan(&person.ID, &person.Name, &person.Phone)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}

	encoder := json.NewEncoder(resp)
	err = encoder.Encode(person)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
}

var DB *sql.DB

type Person struct {
	ID    int64
	Name  string
	Phone string
}


================================================
FILE: ch04/01_welcome/04_long_method_test.go
================================================
package welcome

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLongMethod_happyPath(t *testing.T) {
	// build request
	request := &http.Request{}
	request.PostForm = url.Values{}
	request.PostForm.Add("UserID", "123")

	// mock the database
	var mockDB sqlmock.Sqlmock
	var err error

	DB, mockDB, err = sqlmock.New()
	require.NoError(t, err)
	mockDB.ExpectQuery("SELECT .* FROM people WHERE ID = ?").
		WithArgs(123).
		WillReturnRows(sqlmock.NewRows([]string{"ID", "Name", "Phone"}).AddRow(123, "May", "0123456789"))

	// build response
	response := httptest.NewRecorder()

	// call method
	longMethod(response, request)

	// validate response
	require.Equal(t, http.StatusOK, response.Code)

	// validate the JSON
	responseBytes, err := ioutil.ReadAll(response.Body)
	require.NoError(t, err)

	expectedJSON := `{"ID":123,"Name":"May","Phone":"0123456789"}` + "\n"
	assert.Equal(t, expectedJSON, string(responseBytes))
}


================================================
FILE: ch04/01_welcome/05_short_methods.go
================================================
package welcome

import (
	"encoding/json"
	"net/http"
	"strconv"
)

func shortMethods(resp http.ResponseWriter, req *http.Request) {
	userID, err := extractUserID(req)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}

	person, err := loadPerson(userID)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}

	outputPerson(resp, person)
}

func extractUserID(req *http.Request) (int64, error) {
	err := req.ParseForm()
	if err != nil {
		return 0, err
	}

	return strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
}

func loadPerson(userID int64) (*Person, error) {
	row := DB.QueryRow("SELECT * FROM people WHERE ID = ?", userID)

	person := &Person{}
	err := row.Scan(&person.ID, &person.Name, &person.Phone)
	if err != nil {
		return nil, err
	}
	return person, nil
}

func outputPerson(resp http.ResponseWriter, person *Person) {
	encoder := json.NewEncoder(resp)
	err := encoder.Encode(person)
	if err != nil {
		resp.WriteHeader(http.StatusInternalServerError)
		return
	}
}


================================================
FILE: ch04/03_known_issues/01_data_and_rest/get_example.go
================================================
//+build ignore

package data_and_rest

import (
	"encoding/json"
	"io"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
)

// output the supplied person as JSON
func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) error {
	// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
	return json.NewEncoder(writer).Encode(person)
}


================================================
FILE: ch04/03_known_issues/02_config_coupling/config.go
================================================
package config_coupling

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/03_known_issues/02_config_coupling/currency"
)

type Config struct {
	DefaultCurrency currency.Currency `json:"default_currency"`
}


================================================
FILE: ch04/03_known_issues/02_config_coupling/currency/currency.go
================================================
package currency

import (
	"encoding/json"
	"fmt"
)

// Currency is a custom type; used for convenience and code readability
type Currency string

// UnmarshalJSON implements json.Unmarshaler
func (c *Currency) UnmarshalJSON(in []byte) error {
	var s string
	err := json.Unmarshal(in, &s)
	if err != nil {
		return err
	}

	currency, valid := validCurrencies[s]
	if !valid {
		return fmt.Errorf("'%s' is not a valid currency", s)
	}

	*c = currency

	return nil
}

const (
	AUD = Currency("AUD")
	CNY = Currency("CNY")
	EUR = Currency("EUR")
	USD = Currency("USD")
)

// a map of valid currencies
var validCurrencies = map[string]Currency{
	string(AUD): AUD,
	string(CNY): CNY,
	string(EUR): EUR,
	string(USD): USD,
}


================================================
FILE: ch04/acme/internal/config/config.go
================================================
package config

import (
	"encoding/json"
	"io/ioutil"
	"os"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
)

// DefaultEnvVar is the default environment variable the points to the config file
const DefaultEnvVar = "ACME_CONFIG"

// App is the application config
var App *Config

// Config defines the JSON format for the config file
type Config struct {
	// DSN is the data source name (format: https://github.com/go-sql-driver/mysql/#dsn-data-source-name)
	DSN string

	// Address is the IP address and port to bind this rest to
	Address string

	// BasePrice is the price of registration
	BasePrice float64

	// ExchangeRateBaseURL is the server and protocol part of the URL from which to load the exchange rate
	ExchangeRateBaseURL string

	// ExchangeRateAPIKey is the API for the exchange rate API
	ExchangeRateAPIKey string
}

// Load returns the config loaded from environment
func init() {
	filename, found := os.LookupEnv(DefaultEnvVar)
	if !found {
		logging.L.Error("failed to locate file specified by %s", DefaultEnvVar)
		return
	}

	_ = load(filename)
}

func load(filename string) error {
	App = &Config{}
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		logging.L.Error("failed to read config file. err: %s", err)
		return err
	}

	err = json.Unmarshal(bytes, App)
	if err != nil {
		logging.L.Error("failed to parse config file. err : %s", err)
		return err
	}

	return nil
}


================================================
FILE: ch04/acme/internal/config/config_test.go
================================================
package config

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLoad(t *testing.T) {
	scenarios := []struct {
		desc           string
		in             string
		expectedConfig *Config
		expectError    bool
	}{
		{
			desc: "happy path",
			in:   "../../../../default-config.json",
			expectedConfig: &Config{
				DSN:                 "[insert your db config here]",
				Address:             "0.0.0.0:8080",
				BasePrice:           100.00,
				ExchangeRateBaseURL: "http://apilayer.net",
				ExchangeRateAPIKey:  "[insert your API key here]",
			},
			expectError: false,
		},
		{
			desc:           "invalid path",
			in:             "invalid.json",
			expectedConfig: &Config{},
			expectError:    true,
		},
	}

	for _, s := range scenarios {
		scenario := s
		t.Run(scenario.desc, func(t *testing.T) {
			resultErr := load(scenario.in)
			require.Equal(t, scenario.expectError, resultErr != nil, "err: %s", resultErr)
			assert.Equal(t, scenario.expectedConfig, App, scenario.desc)
		})
	}

}


================================================
FILE: ch04/acme/internal/logging/logging.go
================================================
package logging

import (
	"fmt"
)

// L is the global instance of the logger
var L = &LoggerStdOut{}

// LoggerStdOut logs to std out
type LoggerStdOut struct{}

// Debug logs messages at DEBUG level
func (l LoggerStdOut) Debug(message string, args ...interface{}) {
	fmt.Printf("[DEBUG] "+message, args...)
}

// Info logs messages at INFO level
func (l LoggerStdOut) Info(message string, args ...interface{}) {
	fmt.Printf("[INFO] "+message, args...)
}

// Warn logs messages at WARN level
func (l LoggerStdOut) Warn(message string, args ...interface{}) {
	fmt.Printf("[WARN] "+message, args...)
}

// Error logs messages at ERROR level
func (l LoggerStdOut) Error(message string, args ...interface{}) {
	fmt.Printf("[ERROR] "+message, args...)
}


================================================
FILE: ch04/acme/internal/modules/data/data.go
================================================
package data

import (
	"database/sql"
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
	// import the MySQL Driver
	_ "github.com/go-sql-driver/mysql"
)

const (
	// default person id (returned on error)
	defaultPersonID = 0
)

var (
	db *sql.DB

	// ErrNotFound is returned when the no records where matched by the query
	ErrNotFound = errors.New("not found")
)

func getDB() (*sql.DB, error) {
	if db == nil {
		if config.App == nil {
			return nil, errors.New("config is not initialized")
		}

		var err error
		db, err = sql.Open("mysql", config.App.DSN)
		if err != nil {
			// if the DB cannot be accessed we are dead
			panic(err.Error())
		}
	}

	return db, nil
}

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}

// Save will save the supplied person and return the ID of the newly created person or an error.
// Errors returned are caused by the underlying database or our connection to it.
func Save(in *Person) (int, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return defaultPersonID, err
	}

	// perform DB insert
	query := "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
	result, err := db.Exec(query, in.FullName, in.Phone, in.Currency, in.Price)
	if err != nil {
		logging.L.Error("failed to save person into DB. err: %s", err)
		return defaultPersonID, err
	}

	// retrieve and return the ID of the person created
	id, err := result.LastInsertId()
	if err != nil {
		logging.L.Error("failed to retrieve id of last saved person. err: %s", err)
		return defaultPersonID, err
	}
	return int(id), nil
}

// LoadAll will attempt to load all people in the database
// It will return ErrNotFound when there are not people in the database
// Any other errors returned are caused by the underlying database or our connection to it.
func LoadAll() ([]*Person, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return nil, err
	}

	// perform DB select
	query := "SELECT id, fullname, phone, currency, price FROM person"
	rows, err := db.Query(query)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = rows.Close()
	}()

	var out []*Person

	for rows.Next() {
		// retrieve columns and populate the person object
		record, err := populatePerson(rows.Scan)
		if err != nil {
			logging.L.Error("failed to convert query result. err: %s", err)
			return nil, err
		}

		out = append(out, record)
	}

	if len(out) == 0 {
		logging.L.Warn("no people found in the database.")
		return nil, ErrNotFound
	}

	return out, nil
}

// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return nil, err
	}

	// perform DB select
	query := "SELECT id, fullname, phone, currency, price FROM person WHERE id = ? LIMIT 1"
	row := db.QueryRow(query, ID)

	// retrieve columns and populate the person object
	out, err := populatePerson(row.Scan)
	if err != nil {
		if err == sql.ErrNoRows {
			logging.L.Warn("failed to load requested person '%d'. err: %s", ID, err)
			return nil, ErrNotFound
		}

		logging.L.Error("failed to convert query result. err: %s", err)
		return nil, err
	}
	return out, nil
}

// custom type so we can convert sql results to easily
type scanner func(dest ...interface{}) error

// reduce the duplication (and maintenance) between sql.Row and sql.Rows usage
func populatePerson(scanner scanner) (*Person, error) {
	out := &Person{}
	err := scanner(&out.ID, &out.FullName, &out.Phone, &out.Currency, &out.Price)
	return out, err
}


================================================
FILE: ch04/acme/internal/modules/data/data_test.go
================================================
package data

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestData_happyPath(t *testing.T) {
	in := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// save
	resultID, err := Save(in)
	require.Nil(t, err)
	assert.True(t, resultID > 0)

	// load
	returned, err := Load(resultID)
	require.NoError(t, err)

	in.ID = resultID
	assert.Equal(t, in, returned)

	// load all
	all, err := LoadAll()
	require.NoError(t, err)
	assert.True(t, len(all) > 0)
}


================================================
FILE: ch04/acme/internal/modules/exchange/converter.go
================================================
package exchange

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
)

const (
	// request URL for the exchange rate API
	urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"

	// default price that is sent when an error occurs
	defaultPrice = 0.0
)

// Converter will convert the base price to the currency supplied
// Note: we are expecting sane inputs and therefore skipping input validation
type Converter struct{}

// Do will perform the conversion
func (c *Converter) Do(basePrice float64, currency string) (float64, error) {
	// load rate from the external API
	response, err := c.loadRateFromServer(currency)
	if err != nil {
		return defaultPrice, err
	}

	// extract rate from response
	rate, err := c.extractRate(response, currency)
	if err != nil {
		return defaultPrice, err
	}

	// apply rate and round to 2 decimal places
	return math.Floor((basePrice/rate)*100) / 100, nil
}

// load rate from the external API
func (c *Converter) loadRateFromServer(currency string) (*http.Response, error) {
	// build the request
	url := fmt.Sprintf(urlFormat,
		config.App.ExchangeRateBaseURL,
		config.App.ExchangeRateAPIKey,
		currency)

	// perform request
	response, err := http.Get(url)
	if err != nil {
		logging.L.Warn("[exchange] failed to load. err: %s", err)
		return nil, err
	}

	if response.StatusCode != http.StatusOK {
		err = fmt.Errorf("request failed with code %d", response.StatusCode)
		logging.L.Warn("[exchange] %s", err)
		return nil, err
	}

	return response, nil
}

func (c *Converter) extractRate(response *http.Response, currency string) (float64, error) {
	defer func() {
		_ = response.Body.Close()
	}()

	// extract data from response
	data, err := c.extractResponse(response)
	if err != nil {
		return defaultPrice, err
	}

	// pull rate from response data
	rate, found := data.Quotes["USD"+currency]
	if !found {
		err = fmt.Errorf("response did not include expected currency '%s'", currency)
		logging.L.Error("[exchange] %s", err)
		return defaultPrice, err
	}

	// happy path
	return rate, nil
}

func (c *Converter) extractResponse(response *http.Response) (*apiResponseFormat, error) {
	payload, err := ioutil.ReadAll(response.Body)
	if err != nil {
		logging.L.Error("[exchange] failed to ready response body. err: %s", err)
		return nil, err
	}

	data := &apiResponseFormat{}
	err = json.Unmarshal(payload, data)
	if err != nil {
		logging.L.Error("[exchange] error converting response. err: %s", err)
		return nil, err
	}

	// happy path
	return data, nil
}

// the response format from the exchange rate API
type apiResponseFormat struct {
	Quotes map[string]float64 `json:"quotes"`
}


================================================
FILE: ch04/acme/internal/modules/get/get.go
================================================
package get

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
)

var (
	// error thrown when the requested person is not in the database
	errPersonNotFound = errors.New("person not found")
)

// Getter will attempt to load a person.
// It can return an error caused by the data layer or when the requested person is not found
type Getter struct {
}

// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
	// load person from the data layer
	person, err := data.Load(ID)
	if err != nil {
		if err == data.ErrNotFound {
			// By converting the error we are encapsulating the implementation details from our users.
			return nil, errPersonNotFound
		}
		return nil, err
	}

	return person, err
}


================================================
FILE: ch04/acme/internal/modules/get/go_test.go
================================================
package get

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetter_Do(t *testing.T) {
	// inputs
	ID := 1
	name := "John"

	// call method
	getter := &Getter{}
	person, err := getter.Do(ID)

	// validate expectations
	require.NoError(t, err)
	assert.Equal(t, ID, person.ID)
	assert.Equal(t, name, person.FullName)
}


================================================
FILE: ch04/acme/internal/modules/list/list.go
================================================
package list

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
)

var (
	// error thrown when there are no people in the database
	errPeopleNotFound = errors.New("no people found")
)

// Lister will attempt to load all people in the database.
// It can return an error caused by the data layer
type Lister struct {
}

// Do will load the people from the data layer
func (l *Lister) Do() ([]*data.Person, error) {
	// load all people
	people, err := l.load()
	if err != nil {
		return nil, err
	}

	if len(people) == 0 {
		// special processing for 0 people returned
		return nil, errPeopleNotFound
	}

	return people, nil
}

// load all people
func (l *Lister) load() ([]*data.Person, error) {
	people, err := data.LoadAll()
	if err != nil {
		if err == data.ErrNotFound {
			// By converting the error we are encapsulating the implementation details from our users.
			return nil, errPeopleNotFound
		}
		return nil, err
	}

	return people, nil
}


================================================
FILE: ch04/acme/internal/modules/list/list_test.go
================================================
package list

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLister_Do(t *testing.T) {
	// call method
	lister := &Lister{}
	persons, err := lister.load()

	// validate expectations
	require.NoError(t, err)
	assert.True(t, len(persons) >= 4)
}


================================================
FILE: ch04/acme/internal/modules/register/register.go
================================================
package register

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/exchange"
)

const (
	// default person id (returned on error)
	defaultPersonID = 0
)

var (
	// validation errors
	errNameMissing     = errors.New("name is missing")
	errPhoneMissing    = errors.New("phone is missing")
	errCurrencyMissing = errors.New("currency is missing")
	errInvalidCurrency = errors.New("currency is invalid, supported types are AUD, CNY, EUR, GBP, JPY, MYR, SGD, USD")

	// a little trick to make checking for supported currencies easier
	supportedCurrencies = map[string]struct{}{
		"AUD": {},
		"CNY": {},
		"EUR": {},
		"GBP": {},
		"JPY": {},
		"MYR": {},
		"SGD": {},
		"USD": {},
	}
)

// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}

// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
	// validate the request
	err := r.validateInput(in)
	if err != nil {
		logging.L.Warn("input validation failed with err: %s", err)
		return defaultPersonID, err
	}

	// get price in the requested currency
	price, err := r.getPrice(in.Currency)
	if err != nil {
		return defaultPersonID, err
	}

	// save registration
	id, err := r.save(in, price)
	if err != nil {
		// no need to log here as we expect the data layer to do so
		return defaultPersonID, err
	}

	return id, nil
}

// validate input and return error on fail
func (r *Registerer) validateInput(in *data.Person) error {
	if in.FullName == "" {
		return errNameMissing
	}
	if in.Phone == "" {
		return errPhoneMissing
	}
	if in.Currency == "" {
		return errCurrencyMissing
	}

	if _, found := supportedCurrencies[in.Currency]; !found {
		return errInvalidCurrency
	}

	// happy path
	return nil
}

// get price in the requested currency
func (r *Registerer) getPrice(currency string) (float64, error) {
	converter := &exchange.Converter{}
	price, err := converter.Do(config.App.BasePrice, currency)
	if err != nil {
		logging.L.Warn("failed to convert the price. err: %s", err)
		return defaultPersonID, err
	}

	return price, nil
}

// save the registration
func (r *Registerer) save(in *data.Person, price float64) (int, error) {
	person := &data.Person{
		FullName: in.FullName,
		Phone:    in.Phone,
		Currency: in.Currency,
		Price:    price,
	}
	return data.Save(person)
}


================================================
FILE: ch04/acme/internal/modules/register/register_test.go
================================================
package register

import (
	"testing"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRegisterer_Do(t *testing.T) {
	// inputs
	in := &data.Person{
		FullName: "Chang",
		Phone:    "11122233345",
		Currency: "CNY",
	}

	// call method
	registerer := &Registerer{}
	ID, err := registerer.Do(in)

	// validate expectations
	require.NoError(t, err)
	assert.True(t, ID > 0)
}


================================================
FILE: ch04/acme/internal/rest/common_test.go
================================================
package rest

import (
	"context"
	"net"
)

func getOpenPort() (string, error) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return "", err
	}

	address := listener.Addr().String()
	listener.Close()

	return address, nil
}

func startServer(ctx context.Context) (string, error) {
	// get open port
	address, err := getOpenPort()
	if err != nil {
		return "", err
	}

	// start a server
	server := New(address)
	go server.Listen(ctx.Done())

	// wait for server to be ready
	dialer := &net.Dialer{}
	for {
		conn, _ := dialer.DialContext(ctx, "tcp", address)
		if conn != nil {
			defer conn.Close()

			return address, nil
		}

		select {
		case <-ctx.Done():
			return "", ctx.Err()

		default:
			// try again
		}
	}

	return address, nil
}


================================================
FILE: ch04/acme/internal/rest/get.go
================================================
package rest

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/get"
	"github.com/gorilla/mux"
)

const (
	// default person id (returned on error)
	defaultPersonID = 0
)

// GetHandler is the HTTP handler for the "Get Person" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400
// or "not found" HTTP 404
// There are some programmer errors possible but hopefully these will be caught in testing.
type GetHandler struct {
}

// ServeHTTP implements http.Handler
func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// extract person id from request
	id, err := h.extractID(request)
	if err != nil {
		// output error
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// attempt get
	getter := get.Getter{}
	person, err := getter.Do(id)
	if err != nil {
		// not need to log here as we can expect other layers to do so
		response.WriteHeader(http.StatusNotFound)
		return
	}

	// happy path
	err = h.writeJSON(response, person)
	if err != nil {
		// this error should not happen but if it does there is nothing we can do to recover
		response.WriteHeader(http.StatusInternalServerError)
	}
}

// extract the person ID from the request
func (h *GetHandler) extractID(request *http.Request) (int, error) {
	// ID is part of the URL, so we extract it from there
	vars := mux.Vars(request)
	idAsString, exists := vars["id"]
	if !exists {
		// log and return error
		err := errors.New("[get] person id missing from request")
		logging.L.Warn(err.Error())
		return defaultPersonID, err
	}

	// convert ID to int
	id, err := strconv.Atoi(idAsString)
	if err != nil {
		// log and return error
		err = fmt.Errorf("[get] failed to convert person id into a number. err: %s", err)
		logging.L.Error(err.Error())
		return defaultPersonID, err
	}

	return id, nil
}

// output the supplied person as JSON
func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) error {
	output := &getResponseFormat{
		ID:       person.ID,
		FullName: person.FullName,
		Phone:    person.Phone,
		Currency: person.Currency,
		Price:    person.Price,
	}

	// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
	return json.NewEncoder(writer).Encode(output)
}

// the JSON response format
type getResponseFormat struct {
	ID       int     `json:"id"`
	FullName string  `json:"name"`
	Phone    string  `json:"phone"`
	Currency string  `json:"currency"`
	Price    float64 `json:"price"`
}


================================================
FILE: ch04/acme/internal/rest/get_test.go
================================================
package rest

import (
	"context"
	"io/ioutil"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	response, err := http.Get("http://" + address + "/person/1/")

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.StatusCode)

	expectedPayload := []byte(`{"id":1,"name":"John","phone":"0123456780","currency":"USD","price":100}` + "\n")
	payload, _ := ioutil.ReadAll(response.Body)
	defer response.Body.Close()

	assert.Equal(t, expectedPayload, payload)
}


================================================
FILE: ch04/acme/internal/rest/list.go
================================================
package rest

import (
	"encoding/json"
	"io"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/list"
)

// ListHandler is the HTTP handler for the "List Do people" endpoint
// In this simplified example we are assuming all possible errors are system errors (HTTP 500)
type ListHandler struct {
}

// ServeHTTP implements http.Handler
func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// attempt loadAll
	lister := list.Lister{}
	people, err := lister.Do()
	if err != nil {
		// not need to log here as we can expect other layers to do so
		response.WriteHeader(http.StatusNotFound)
		return
	}

	// happy path
	err = h.writeJSON(response, people)
	if err != nil {
		// this error should not happen but if it does there is nothing we can do to recover
		response.WriteHeader(http.StatusInternalServerError)
	}
}

// output the result as JSON
func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Person) error {
	output := &listResponseFormat{
		People: make([]*listResponseItemFormat, len(people)),
	}

	for index, record := range people {
		output.People[index] = &listResponseItemFormat{
			ID:       record.ID,
			FullName: record.FullName,
			Phone:    record.Phone,
		}
	}

	// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
	return json.NewEncoder(writer).Encode(output)
}

type listResponseFormat struct {
	People []*listResponseItemFormat `json:"people"`
}

type listResponseItemFormat struct {
	ID       int    `json:"id"`
	FullName string `json:"name"`
	Phone    string `json:"phone"`
}


================================================
FILE: ch04/acme/internal/rest/list_test.go
================================================
package rest

import (
	"context"
	"io/ioutil"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestListHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	response, err := http.Get("http://" + address + "/person/list")

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.StatusCode)

	expectedPayload := []byte(`{"people":[{"id":1,"name":"John","phone":"0123456780"},{"id":2,"name":"Paul","phone":"0123456781"},{"id":3,"name":"George","phone":"0123456782"},{"id":4,"name":"Ringo","phone":"0123456783"}`)
	payload, _ := ioutil.ReadAll(response.Body)
	defer response.Body.Close()

	// we have to use contains because other tests add more records
	assert.Contains(t, string(payload), string(expectedPayload))
}


================================================
FILE: ch04/acme/internal/rest/not_found.go
================================================
package rest

import (
	"net/http"
)

func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
	response.WriteHeader(http.StatusNotFound)
	_, _ = response.Write([]byte(`Not found`))
}


================================================
FILE: ch04/acme/internal/rest/not_found_test.go
================================================
package rest

import (
	"context"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestNotFoundHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	response, err := http.Get("http://" + address + "/some-bad-address")

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusNotFound, response.StatusCode)
}


================================================
FILE: ch04/acme/internal/rest/register.go
================================================
package rest

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/register"
)

// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
}

// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// extract payload from request
	requestPayload, err := h.extractPayload(request)
	if err != nil {
		// output error
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// register person
	id, err := h.register(requestPayload)
	if err != nil {
		// not need to log here as we can expect other layers to do so
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// happy path
	response.Header().Add("Location", fmt.Sprintf("/person/%d/", id))
	response.WriteHeader(http.StatusCreated)
}

// extract payload from request
func (h *RegisterHandler) extractPayload(request *http.Request) (*registerRequest, error) {
	requestPayload := &registerRequest{}

	decoder := json.NewDecoder(request.Body)
	err := decoder.Decode(requestPayload)
	if err != nil {
		return nil, err
	}

	return requestPayload, nil
}

// call the logic layer
func (h *RegisterHandler) register(requestPayload *registerRequest) (int, error) {
	person := &data.Person{
		FullName: requestPayload.FullName,
		Phone:    requestPayload.Phone,
		Currency: requestPayload.Currency,
	}

	registerer := &register.Registerer{}
	return registerer.Do(person)
}

// register endpoint request format
type registerRequest struct {
	// FullName of the person
	FullName string `json:"fullName"`
	// Phone of the person
	Phone string `json:"phone"`
	// Currency the wish to register in
	Currency string `json:"currency"`
}


================================================
FILE: ch04/acme/internal/rest/register_test.go
================================================
package rest

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRegisterHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	validRequest := buildValidRequest()
	response, err := http.Post("http://"+address+"/person/register", "application/json", validRequest)

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusCreated, response.StatusCode)
	defer response.Body.Close()

	// call should output the location to the new person
	headerLocation := response.Header.Get("Location")
	assert.Contains(t, headerLocation, "/person/")
}

func buildValidRequest() io.Reader {
	requestData := &registerRequest{
		FullName: "Joan Smith",
		Currency: "AUD",
		Phone:    "01234567890",
	}

	data, _ := json.Marshal(requestData)
	return bytes.NewBuffer(data)
}


================================================
FILE: ch04/acme/internal/rest/server.go
================================================
package rest

import (
	"net/http"

	"github.com/gorilla/mux"
)

// New will create and initialize the server
func New(address string) *Server {
	return &Server{
		address:         address,
		handlerGet:      &GetHandler{},
		handlerList:     &ListHandler{},
		handlerNotFound: notFoundHandler,
		handlerRegister: &RegisterHandler{},
	}
}

// Server is the HTTP REST server
type Server struct {
	address string
	server  *http.Server

	handlerGet      http.Handler
	handlerList     http.Handler
	handlerNotFound http.HandlerFunc
	handlerRegister http.Handler
}

// Listen will start a HTTP rest for this service
func (s *Server) Listen(stop <-chan struct{}) {
	router := s.buildRouter()

	// create the HTTP server
	s.server = &http.Server{
		Handler: router,
		Addr:    s.address,
	}

	// listen for shutdown
	go func() {
		// wait for shutdown signal
		<-stop

		_ = s.server.Close()
	}()

	// start the HTTP server
	_ = s.server.ListenAndServe()
}

// configure the endpoints to handlers
func (s *Server) buildRouter() http.Handler {
	router := mux.NewRouter()

	// map URL endpoints to HTTP handlers
	router.Handle("/person/{id}/", s.handlerGet).Methods("GET")
	router.Handle("/person/list", s.handlerList).Methods("GET")
	router.Handle("/person/register", s.handlerRegister).Methods("POST")

	// convert a "catch all" not found handler
	router.NotFoundHandler = s.handlerNotFound

	return router
}


================================================
FILE: ch04/acme/main.go
================================================
package main

import (
	"context"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/rest"
)

func main() {
	// bind stop channel to context
	ctx := context.Background()

	// start REST server
	server := rest.New(config.App.Address)
	server.Listen(ctx.Done())
}


================================================
FILE: ch04/fake.go
================================================
package ch04

func init() {
	// This file is included so that Go tools (like `go list`) will find Go code in this directory and not error
}


================================================
FILE: ch05/02_advantages/01_function.go
================================================
package advantages

import (
	"encoding/json"
	"io/ioutil"
	"log"
)

func SaveConfig(filename string, cfg *Config) error {
	// convert to JSON
	data, err := json.Marshal(cfg)
	if err != nil {
		return err
	}

	// save file
	err = ioutil.WriteFile(filename, data, 0666)
	if err != nil {
		log.Printf("failed to save file '%s' with err: %s", filename, err)
		return err
	}

	return nil
}

type Config struct {
	Host string
	Port int
}


================================================
FILE: ch05/02_advantages/02_monkey_patched.go
================================================
package advantages

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
)

func SaveConfigPatched(filename string, cfg *Config) error {
	// convert to JSON
	data, err := json.Marshal(cfg)
	if err != nil {
		return err
	}

	// save file
	err = writeFile(filename, data, 0666)
	if err != nil {
		log.Printf("failed to save file '%s' with err: %s", filename, err)
		return err
	}

	return nil
}

// Custom type that allows us to Monkey Patch
var writeFile = ioutil.WriteFile

// Usage
func SaveConfigPatchedUsage() {
	cfg := &Config{
		// build the config
	}

	err := SaveConfigPatched("myfile.json", cfg)
	if err != nil {
		fmt.Printf("failed with err: %s", err)
	}
}


================================================
FILE: ch05/02_advantages/03_injected_lambda.go
================================================
package advantages

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

func SaveConfigInjected(writer fileWriter, filename string, cfg *Config) error {
	// convert to JSON
	data, err := json.Marshal(cfg)
	if err != nil {
		return err
	}

	// save file
	err = writer(filename, data, 0666)
	if err != nil {
		log.Printf("failed to save file '%s' with err: %s", filename, err)
		return err
	}

	return nil
}

// This custom type is not strictly needed but it does make the function
// signature a little cleaner
type fileWriter func(filename string, data []byte, perm os.FileMode) error

// Usage
func SaveConfigInjectedUsage() {
	cfg := &Config{
		// build the config
	}

	err := SaveConfigInjected(ioutil.WriteFile, "myfile.json", cfg)
	if err != nil {
		fmt.Printf("failed with err: %s", err)
	}
}


================================================
FILE: ch05/02_advantages/04_as_object.go
================================================
package advantages

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

type ConfigSaver struct {
	FileWriter func(filename string, data []byte, perm os.FileMode) error
}

func (c ConfigSaver) Save(filename string, cfg *Config) error {
	// convert to JSON
	data, err := json.Marshal(cfg)
	if err != nil {
		return err
	}

	// save file
	err = c.FileWriter(filename, data, 0666)
	if err != nil {
		log.Printf("failed to save file '%s' with err: %s", filename, err)
		return err
	}

	return nil
}

// Usage
func ConfigSaverUsage() {
	cfg := &Config{
		// build the config
	}

	saver := &ConfigSaver{
		FileWriter: ioutil.WriteFile,
	}

	err := saver.Save("myfile.json", cfg)
	if err != nil {
		fmt.Printf("failed with err: %s", err)
	}
}


================================================
FILE: ch05/02_advantages/05_math_rand.go
================================================
package advantages

// A Rand is a source of random numbers.
type Rand struct {
	src Source

	// code removed
}

// Int returns a non-negative pseudo-random int.
func (r *Rand) Int() int {
	// code changed for brevity
	value := r.src.Int63()
	return int(value)
}

/*
 * Top-level convenience functions
 */

var globalRand = New(&lockedSource{})

// Int returns a non-negative pseudo-random int from the default Source.
func Int() int { return globalRand.Int() }

/*
 * Code below here has been modified so that it compiles but does nothing.
 * The original code is: https://golang.org/src/math/rand/rand.go
 */

// New returns a new Rand that uses random values from src
// to generate other random values.
func New(src Source) *Rand {
	// code changed for brevity
	return &Rand{
		src: src,
	}
}

type lockedSource struct {
	// code removed
}

func (l *lockedSource) Int63() int64 {
	// code removed
	return 0
}

// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
type Source interface {
	Int63() int64

	// code removed
}


================================================
FILE: ch05/02_advantages/06_math_rand_test.go
================================================
package advantages

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestInt(t *testing.T) {
	// monkey patch
	defer func(original *Rand) {
		// restore patch after use
		globalRand = original
	}(globalRand)

	// swap out for a predictable outcome
	globalRand = New(&stubSource{})
	// end monkey patch

	// call the function
	result := Int()
	assert.Equal(t, 234, result)
}

// this is a stubbed implementation of Source that returns a predictable value
type stubSource struct {
}

func (s *stubSource) Int63() int64 {
	return 234
}


================================================
FILE: ch05/03_applying/01_simple_sqlmock_test.go
================================================
package applying

import (
	"database/sql"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestSave_happyPath(t *testing.T) {
	// define a mock db
	testDb, dbMock, err := sqlmock.New()
	require.NoError(t, err)

	// clean up afterwards
	defer testDb.Close()

	// define the query we are expecting as regular expression
	queryRegex := `\QINSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)\E`

	// configure the mock db
	dbMock.ExpectExec(queryRegex).WillReturnResult(sqlmock.NewResult(2, 1))

	// inputs
	person := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// call function
	resultID, err := SavePerson(testDb, person)

	// validate result
	require.NoError(t, err)
	assert.Equal(t, 2, resultID)
	assert.NoError(t, dbMock.ExpectationsWereMet())
}

func SavePerson(db *sql.DB, in *Person) (int, error) {
	// perform DB insert
	query := "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
	result, err := db.Exec(query, in.FullName, in.Phone, in.Currency, in.Price)
	if err != nil {
		return 0, err
	}

	// retrieve and return the ID of the person created
	id, err := result.LastInsertId()
	if err != nil {
		return 0, err
	}
	return int(id), nil
}


================================================
FILE: ch05/03_applying/02_load.go
================================================
package applying

import (
	"database/sql"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	// SQL statements as constants (to reduce duplication and maintenance in tests)
	sqlAllColumns = "id, fullname, phone, currency, price"
	sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? LIMIT 1"
)

func TestLoad_happyPath(t *testing.T) {
	expectedResult := &Person{
		ID:       2,
		FullName: "Paul",
		Phone:    "0123456789",
		Currency: "CAD",
		Price:    23.45,
	}

	// define a mock db
	testDb, dbMock, err := sqlmock.New()
	require.NoError(t, err)

	// configure the mock db
	queryRegex := convertSQLToRegex(sqlLoadByID)
	dbMock.ExpectQuery(queryRegex).WillReturnRows(
		sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
			AddRow(2, "Paul", "0123456789", "CAD", 23.45))

	// monkey patching the database
	defer func(original sql.DB) {
		// restore original DB (after test)
		db = &original
	}(*db)

	db = testDb
	// end of monkey patch

	// call function
	result, err := Load(2)

	// validate results
	assert.Equal(t, expectedResult, result)
	assert.NoError(t, err)
	assert.NoError(t, dbMock.ExpectationsWereMet())
}

// convert SQL string to regex by treating the entire query as a literal
func convertSQLToRegex(in string) string {
	return `\Q` + in + `\E`
}

// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
	// code removed/faked for brevity
	return &Person{
		ID:       2,
		FullName: "Paul",
		Phone:    "0123456789",
		Currency: "CAD",
		Price:    23.45,
	}, nil
}

// code removed for brevity
var db = &sql.DB{}

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}


================================================
FILE: ch05/04_disadvantages/01_verbose.go
================================================
package disadvantages

import (
	"encoding/json"
	"io/ioutil"
	"log"
)

func SaveConfig(filename string, cfg *Config) error {
	// convert to JSON
	data, err := json.Marshal(cfg)
	if err != nil {
		return err
	}

	// save file
	err = writeFile(filename, data, 0666)
	if err != nil {
		log.Printf("failed to save file '%s' with err: %s", filename, err)
		return err
	}

	return nil
}

// Custom type that allows
var writeFile = ioutil.WriteFile

type Config struct {
	Host string
	Port int
}


================================================
FILE: ch05/04_disadvantages/02_verbose_test.go
================================================
package disadvantages

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestSaveConfig(t *testing.T) {
	// inputs
	filename := "my-config.json"
	cfg := &Config{
		Host: "localhost",
		Port: 1234,
	}

	// monkey patch the file writer
	defer func(original func(filename string, data []byte, perm os.FileMode) error) {
		// restore the original
		writeFile = original
	}(writeFile)

	writeFile = func(filename string, data []byte, perm os.FileMode) error {
		// output error
		return nil
	}

	// call the function
	err := SaveConfig(filename, cfg)

	// validate the result
	assert.NoError(t, err)
}


================================================
FILE: ch05/04_disadvantages/03_refactored_test.go
================================================
package disadvantages

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestSaveConfig_refactored(t *testing.T) {
	// inputs
	filename := "my-config.json"
	cfg := &Config{
		Host: "localhost",
		Port: 1234,
	}

	// monkey patch the file writer
	defer restoreWriteFile(writeFile)

	writeFile = mockWriteFile(nil)

	// call the function
	err := SaveConfig(filename, cfg)

	// validate the result
	assert.NoError(t, err)
}

func mockWriteFile(result error) func(filename string, data []byte, perm os.FileMode) error {
	return func(filename string, data []byte, perm os.FileMode) error {
		return result
	}
}

// remove the restore function to reduce from 3 lines to 1
func restoreWriteFile(original func(filename string, data []byte, perm os.FileMode) error) {
	// restore the original
	writeFile = original
}


================================================
FILE: ch05/acme/internal/config/config.go
================================================
package config

import (
	"encoding/json"
	"io/ioutil"
	"os"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
)

// DefaultEnvVar is the default environment variable the points to the config file
const DefaultEnvVar = "ACME_CONFIG"

// App is the application config
var App *Config

// Config defines the JSON format for the config file
type Config struct {
	// DSN is the data source name (format: https://github.com/go-sql-driver/mysql/#dsn-data-source-name)
	DSN string

	// Address is the IP address and port to bind this rest to
	Address string

	// BasePrice is the price of registration
	BasePrice float64

	// ExchangeRateBaseURL is the server and protocol part of the URL from which to load the exchange rate
	ExchangeRateBaseURL string

	// ExchangeRateAPIKey is the API for the exchange rate API
	ExchangeRateAPIKey string
}

// Load returns the config loaded from environment
func init() {
	filename, found := os.LookupEnv(DefaultEnvVar)
	if !found {
		logging.L.Error("failed to locate file specified by %s", DefaultEnvVar)
		return
	}

	_ = load(filename)
}

func load(filename string) error {
	App = &Config{}
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		logging.L.Error("failed to read config file. err: %s", err)
		return err
	}

	err = json.Unmarshal(bytes, App)
	if err != nil {
		logging.L.Error("failed to parse config file. err : %s", err)
		return err
	}

	return nil
}


================================================
FILE: ch05/acme/internal/config/config_test.go
================================================
package config

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLoad(t *testing.T) {
	scenarios := []struct {
		desc           string
		in             string
		expectedConfig *Config
		expectError    bool
	}{
		{
			desc: "happy path",
			in:   "../../../../default-config.json",
			expectedConfig: &Config{
				DSN:                 "[insert your db config here]",
				Address:             "0.0.0.0:8080",
				BasePrice:           100.00,
				ExchangeRateBaseURL: "http://apilayer.net",
				ExchangeRateAPIKey:  "[insert your API key here]",
			},
			expectError: false,
		},
		{
			desc:           "invalid path",
			in:             "invalid.json",
			expectedConfig: &Config{},
			expectError:    true,
		},
	}

	for _, s := range scenarios {
		scenario := s
		t.Run(scenario.desc, func(t *testing.T) {
			resultErr := load(scenario.in)
			require.Equal(t, scenario.expectError, resultErr != nil, "err: %s", resultErr)
			assert.Equal(t, scenario.expectedConfig, App, scenario.desc)
		})
	}

}


================================================
FILE: ch05/acme/internal/logging/logging.go
================================================
package logging

import (
	"fmt"
)

// L is the global instance of the logger
var L = &LoggerStdOut{}

// LoggerStdOut logs to std out
type LoggerStdOut struct{}

// Debug logs messages at DEBUG level
func (l LoggerStdOut) Debug(message string, args ...interface{}) {
	fmt.Printf("[DEBUG] "+message, args...)
}

// Info logs messages at INFO level
func (l LoggerStdOut) Info(message string, args ...interface{}) {
	fmt.Printf("[INFO] "+message, args...)
}

// Warn logs messages at WARN level
func (l LoggerStdOut) Warn(message string, args ...interface{}) {
	fmt.Printf("[WARN] "+message, args...)
}

// Error logs messages at ERROR level
func (l LoggerStdOut) Error(message string, args ...interface{}) {
	fmt.Printf("[ERROR] "+message, args...)
}


================================================
FILE: ch05/acme/internal/modules/data/data.go
================================================
package data

import (
	"database/sql"
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
	// import the MySQL Driver
	_ "github.com/go-sql-driver/mysql"
)

const (
	// default person id (returned on error)
	defaultPersonID = 0

	// SQL statements as constants (to reduce duplication and maintenance in tests)
	sqlAllColumns = "id, fullname, phone, currency, price"
	sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
	sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
	sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? LIMIT 1"
)

var (
	db *sql.DB

	// ErrNotFound is returned when the no records where matched by the query
	ErrNotFound = errors.New("not found")
)

var getDB = func() (*sql.DB, error) {
	if db == nil {
		if config.App == nil {
			return nil, errors.New("config is not initialized")
		}

		var err error
		db, err = sql.Open("mysql", config.App.DSN)
		if err != nil {
			// if the DB cannot be accessed we are dead
			panic(err.Error())
		}
	}

	return db, nil
}

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}

// Save will save the supplied person and return the ID of the newly created person or an error.
// Errors returned are caused by the underlying database or our connection to it.
func Save(in *Person) (int, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return defaultPersonID, err
	}

	// perform DB insert
	result, err := db.Exec(sqlInsert, in.FullName, in.Phone, in.Currency, in.Price)
	if err != nil {
		logging.L.Error("failed to save person into DB. err: %s", err)
		return defaultPersonID, err
	}

	// retrieve and return the ID of the person created
	id, err := result.LastInsertId()
	if err != nil {
		logging.L.Error("failed to retrieve id of last saved person. err: %s", err)
		return defaultPersonID, err
	}

	return int(id), nil
}

// LoadAll will attempt to load all people in the database
// It will return ErrNotFound when there are not people in the database
// Any other errors returned are caused by the underlying database or our connection to it.
func LoadAll() ([]*Person, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return nil, err
	}

	// perform DB select
	rows, err := db.Query(sqlLoadAll)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = rows.Close()
	}()

	var out []*Person

	for rows.Next() {
		// retrieve columns and populate the person object
		record, err := populatePerson(rows.Scan)
		if err != nil {
			logging.L.Error("failed to convert query result. err: %s", err)
			return nil, err
		}

		out = append(out, record)
	}

	if len(out) == 0 {
		logging.L.Warn("no people found in the database.")
		return nil, ErrNotFound
	}

	return out, nil
}

// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return nil, err
	}

	// perform DB select
	row := db.QueryRow(sqlLoadByID, ID)

	// retrieve columns and populate the person object
	out, err := populatePerson(row.Scan)
	if err != nil {
		if err == sql.ErrNoRows {
			logging.L.Warn("failed to load requested person '%d'. err: %s", ID, err)
			return nil, ErrNotFound
		}

		logging.L.Error("failed to convert query result. err: %s", err)
		return nil, err
	}
	return out, nil
}

// custom type so we can convert sql results to easily
type scanner func(dest ...interface{}) error

// reduce the duplication (and maintenance) between sql.Row and sql.Rows usage
func populatePerson(scanner scanner) (*Person, error) {
	out := &Person{}
	err := scanner(&out.ID, &out.FullName, &out.Phone, &out.Currency, &out.Price)
	return out, err
}

func init() {
	// ensure the config is loaded and the db initialized
	_, _ = getDB()
}


================================================
FILE: ch05/acme/internal/modules/data/data_test.go
================================================
package data

import (
	"database/sql"
	"errors"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestSave_happyPath(t *testing.T) {
	// define a mock db
	testDb, dbMock, err := sqlmock.New()
	defer testDb.Close()
	require.NoError(t, err)

	// configure the mock db
	queryRegex := convertSQLToRegex(sqlInsert)
	dbMock.ExpectExec(queryRegex).WillReturnResult(sqlmock.NewResult(2, 1))

	// monkey patching starts here
	defer func(original sql.DB) {
		// restore original DB (after test)
		db = &original
	}(*db)

	// replace db for this test
	db = testDb
	// end of monkey patch

	// inputs
	in := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// call function
	resultID, err := Save(in)

	// validate result
	require.NoError(t, err)
	assert.Equal(t, 2, resultID)
	assert.NoError(t, dbMock.ExpectationsWereMet())
}

func TestSave_insertError(t *testing.T) {
	// define a mock db
	testDb, dbMock, err := sqlmock.New()
	defer testDb.Close()

	require.NoError(t, err)

	// configure the mock db
	queryRegex := convertSQLToRegex(sqlInsert)
	dbMock.ExpectExec(queryRegex).WillReturnError(errors.New("failed to insert"))

	// monkey patching starts here
	defer func(original sql.DB) {
		// restore original DB (after test)
		db = &original
	}(*db)

	// replace db for this test
	db = testDb
	// end of monkey patch

	// inputs
	in := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// call function
	resultID, err := Save(in)

	// validate result
	require.Error(t, err)
	assert.Equal(t, defaultPersonID, resultID)
	assert.NoError(t, dbMock.ExpectationsWereMet())
}

func TestSave_getDBError(t *testing.T) {
	// monkey patching starts here
	defer func(original func() (*sql.DB, error)) {
		// restore original DB (after test)
		getDB = original
	}(getDB)

	// replace getDB() function for this test
	getDB = func() (*sql.DB, error) {
		return nil, errors.New("getDB() failed")
	}
	// end of monkey patch

	// inputs
	in := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// call function
	resultID, err := Save(in)
	require.Error(t, err)
	assert.Equal(t, defaultPersonID, resultID)
}

func TestLoadAll_tableDrivenTest(t *testing.T) {
	scenarios := []struct {
		desc            string
		configureMockDB func(sqlmock.Sqlmock)
		expectedResults []*Person
		expectError     bool
	}{
		{
			desc: "happy path",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnRows(
					sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
						AddRow(1, "John", "0123456789", "AUD", 12.34))
			},
			expectedResults: []*Person{
				{
					ID:       1,
					FullName: "John",
					Phone:    "0123456789",
					Currency: "AUD",
					Price:    12.34,
				},
			},
			expectError: false,
		},
		{
			desc: "load error",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
			},
			expectedResults: nil,
			expectError:     true,
		},
	}

	for _, scenario := range scenarios {
		// define a mock db
		testDb, dbMock, err := sqlmock.New()
		require.NoError(t, err)

		// configure the mock db
		scenario.configureMockDB(dbMock)

		// monkey patch the db for this test
		original := *db
		db = testDb

		// call function
		results, err := LoadAll()

		// validate results
		assert.Equal(t, scenario.expectedResults, results, scenario.desc)
		assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
		assert.NoError(t, dbMock.ExpectationsWereMet())

		// restore original DB (after test)
		db = &original
		testDb.Close()
	}
}

func TestLoad_tableDrivenTest(t *testing.T) {
	scenarios := []struct {
		desc            string
		configureMockDB func(sqlmock.Sqlmock)
		expectedResult  *Person
		expectError     bool
	}{
		{
			desc: "happy path",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnRows(
					sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
						AddRow(2, "Paul", "0123456789", "CAD", 23.45))
			},
			expectedResult: &Person{
				ID:       2,
				FullName: "Paul",
				Phone:    "0123456789",
				Currency: "CAD",
				Price:    23.45,
			},
			expectError: false,
		},
		{
			desc: "load error",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
			},
			expectedResult: nil,
			expectError:    true,
		},
	}

	for _, scenario := range scenarios {
		// define a mock db
		testDb, dbMock, err := sqlmock.New()
		require.NoError(t, err)

		// configure the mock db
		scenario.configureMockDB(dbMock)

		// monkey db for this test
		original := *db
		db = testDb

		// call function
		result, err := Load(2)

		// validate results
		assert.Equal(t, scenario.expectedResult, result, scenario.desc)
		assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
		assert.NoError(t, dbMock.ExpectationsWereMet())

		// restore original DB (after test)
		db = &original
		testDb.Close()
	}
}

// convert SQL string to regex by treating the entire query as a literal
func convertSQLToRegex(in string) string {
	return `\Q` + in + `\E`
}


================================================
FILE: ch05/acme/internal/modules/exchange/converter.go
================================================
package exchange

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
)

const (
	// request URL for the exchange rate API
	urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"

	// default price that is sent when an error occurs
	defaultPrice = 0.0
)

// Converter will convert the base price to the currency supplied
// Note: we are expecting sane inputs and therefore skipping input validation
type Converter struct{}

// Do will perform the conversion
func (c *Converter) Do(basePrice float64, currency string) (float64, error) {
	// load rate from the external API
	response, err := c.loadRateFromServer(currency)
	if err != nil {
		return defaultPrice, err
	}

	// extract rate from response
	rate, err := c.extractRate(response, currency)
	if err != nil {
		return defaultPrice, err
	}

	// apply rate and round to 2 decimal places
	return math.Floor((basePrice/rate)*100) / 100, nil
}

// load rate from the external API
func (c *Converter) loadRateFromServer(currency string) (*http.Response, error) {
	// build the request
	url := fmt.Sprintf(urlFormat,
		config.App.ExchangeRateBaseURL,
		config.App.ExchangeRateAPIKey,
		currency)

	// perform request
	response, err := http.Get(url)
	if err != nil {
		logging.L.Warn("[exchange] failed to load. err: %s", err)
		return nil, err
	}

	if response.StatusCode != http.StatusOK {
		err = fmt.Errorf("request failed with code %d", response.StatusCode)
		logging.L.Warn("[exchange] %s", err)
		return nil, err
	}

	return response, nil
}

func (c *Converter) extractRate(response *http.Response, currency string) (float64, error) {
	defer func() {
		_ = response.Body.Close()
	}()

	// extract data from response
	data, err := c.extractResponse(response)
	if err != nil {
		return defaultPrice, err
	}

	// pull rate from response data
	rate, found := data.Quotes["USD"+currency]
	if !found {
		err = fmt.Errorf("response did not include expected currency '%s'", currency)
		logging.L.Error("[exchange] %s", err)
		return defaultPrice, err
	}

	// happy path
	return rate, nil
}

func (c *Converter) extractResponse(response *http.Response) (*apiResponseFormat, error) {
	payload, err := ioutil.ReadAll(response.Body)
	if err != nil {
		logging.L.Error("[exchange] failed to ready response body. err: %s", err)
		return nil, err
	}

	data := &apiResponseFormat{}
	err = json.Unmarshal(payload, data)
	if err != nil {
		logging.L.Error("[exchange] error converting response. err: %s", err)
		return nil, err
	}

	// happy path
	return data, nil
}

// the response format from the exchange rate API
type apiResponseFormat struct {
	Quotes map[string]float64 `json:"quotes"`
}


================================================
FILE: ch05/acme/internal/modules/get/get.go
================================================
package get

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
)

var (
	// error thrown when the requested person is not in the database
	errPersonNotFound = errors.New("person not found")
)

// Getter will attempt to load a person.
// It can return an error caused by the data layer or when the requested person is not found
type Getter struct {
}

// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
	// load person from the data layer
	person, err := loader(ID)
	if err != nil {
		if err == data.ErrNotFound {
			// By converting the error we are hiding the implementation details from our users.
			return nil, errPersonNotFound
		}
		return nil, err
	}

	return person, err
}

// this function as a variable allows us to Monkey Patch during testing
var loader = data.Load


================================================
FILE: ch05/acme/internal/modules/get/go_test.go
================================================
package get

import (
	"errors"
	"testing"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetter_Do_happyPath(t *testing.T) {
	// inputs
	ID := 1234

	// monkey patch calls to the data package
	defer func(original func(ID int) (*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func(ID int) (*data.Person, error) {
		result := &data.Person{
			ID:       1234,
			FullName: "Doug",
		}
		var resultErr error

		return result, resultErr
	}
	// end of monkey patch

	// call method
	getter := &Getter{}
	person, err := getter.Do(ID)

	// validate expectations
	require.NoError(t, err)
	assert.Equal(t, ID, person.ID)
	assert.Equal(t, "Doug", person.FullName)
}

func TestGetter_Do_noSuchPerson(t *testing.T) {
	// inputs
	ID := 5678

	// monkey patch calls to the data package
	defer func(original func(ID int) (*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func(ID int) (*data.Person, error) {
		var result *data.Person
		resultErr := data.ErrNotFound

		return result, resultErr
	}
	// end of monkey patch

	// call method
	getter := &Getter{}
	person, err := getter.Do(ID)

	// validate expectations
	require.Equal(t, errPersonNotFound, err)
	assert.Nil(t, person)
}

func TestGetter_Do_error(t *testing.T) {
	// inputs
	ID := 1234

	// monkey patch calls to the data package
	defer func(original func(ID int) (*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func(ID int) (*data.Person, error) {
		var result *data.Person
		resultErr := errors.New("failed to load person")

		return result, resultErr
	}
	// end of monkey patch

	// call method
	getter := &Getter{}
	person, err := getter.Do(ID)

	// validate expectations
	require.Error(t, err)
	assert.Nil(t, person)
}


================================================
FILE: ch05/acme/internal/modules/list/list.go
================================================
package list

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
)

var (
	// error thrown when there are no people in the database
	errPeopleNotFound = errors.New("no people found")
)

// Lister will attempt to load all people in the database.
// It can return an error caused by the data layer
type Lister struct {
}

// Do will load the people from the data layer
func (l *Lister) Do() ([]*data.Person, error) {
	// load all people
	people, err := l.load()
	if err != nil {
		return nil, err
	}

	if len(people) == 0 {
		// special processing for 0 people returned
		return nil, errPeopleNotFound
	}

	return people, nil
}

// load all people
func (l *Lister) load() ([]*data.Person, error) {
	people, err := loader()
	if err != nil {
		if err == data.ErrNotFound {
			// By converting the error we are encapsulating the implementation details from our users.
			return nil, errPeopleNotFound
		}
		return nil, err
	}

	return people, nil
}

// this function as a variable allows us to Monkey Patch during testing
var loader = data.LoadAll


================================================
FILE: ch05/acme/internal/modules/list/list_test.go
================================================
package list

import (
	"errors"
	"testing"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLister_Do_happyPath(t *testing.T) {
	// monkey patch calls to the data package
	defer func(original func() ([]*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func() ([]*data.Person, error) {
		result := []*data.Person{
			{
				ID:       1234,
				FullName: "Sally",
			},
			{
				ID:       5678,
				FullName: "Jane",
			},
		}
		var resultErr error

		return result, resultErr
	}
	// end of monkey patch

	// call method
	lister := &Lister{}
	persons, err := lister.load()

	// validate expectations
	require.NoError(t, err)
	assert.Equal(t, 2, len(persons))
}

func TestLister_Do_noResults(t *testing.T) {
	// monkey patch calls to the data package
	defer func(original func() ([]*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func() ([]*data.Person, error) {
		var result []*data.Person
		resultErr := data.ErrNotFound

		return result, resultErr
	}
	// end of monkey patch

	// call method
	lister := &Lister{}
	persons, err := lister.load()

	// validate expectations
	require.Equal(t, errPeopleNotFound, err)
	assert.Equal(t, 0, len(persons))
}

func TestLister_Do_error(t *testing.T) {
	// monkey patch calls to the data package
	defer func(original func() ([]*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func() ([]*data.Person, error) {
		var result []*data.Person
		resultErr := errors.New("failed to load people")

		return result, resultErr
	}
	// end of monkey patch

	// call method
	lister := &Lister{}
	persons, err := lister.load()

	// validate expectations
	require.Error(t, err)
	assert.Equal(t, 0, len(persons))
}


================================================
FILE: ch05/acme/internal/modules/register/register.go
================================================
package register

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/exchange"
)

const (
	// default person id (returned on error)
	defaultPersonID = 0
)

var (
	// validation errors
	errNameMissing     = errors.New("name is missing")
	errPhoneMissing    = errors.New("phone is missing")
	errCurrencyMissing = errors.New("currency is missing")
	errInvalidCurrency = errors.New("currency is invalid, supported types are AUD, CNY, EUR, GBP, JPY, MYR, SGD, USD")

	// a little trick to make checking for supported currencies easier
	supportedCurrencies = map[string]struct{}{
		"AUD": {},
		"CNY": {},
		"EUR": {},
		"GBP": {},
		"JPY": {},
		"MYR": {},
		"SGD": {},
		"USD": {},
	}
)

// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}

// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
	// validate the request
	err := r.validateInput(in)
	if err != nil {
		logging.L.Warn("input validation failed with err: %s", err)
		return defaultPersonID, err
	}

	// get price in the requested currency
	price, err := r.getPrice(in.Currency)
	if err != nil {
		return defaultPersonID, err
	}

	// save registration
	id, err := r.save(in, price)
	if err != nil {
		// no need to log here as we expect the data layer to do so
		return defaultPersonID, err
	}

	return id, nil
}

// validate input and return error on fail
func (r *Registerer) validateInput(in *data.Person) error {
	if in.FullName == "" {
		return errNameMissing
	}
	if in.Phone == "" {
		return errPhoneMissing
	}
	if in.Currency == "" {
		return errCurrencyMissing
	}

	if _, found := supportedCurrencies[in.Currency]; !found {
		return errInvalidCurrency
	}

	// happy path
	return nil
}

// get price in the requested currency
func (r *Registerer) getPrice(currency string) (float64, error) {
	converter := &exchange.Converter{}
	price, err := converter.Do(config.App.BasePrice, currency)
	if err != nil {
		logging.L.Warn("failed to convert the price. err: %s", err)
		return defaultPersonID, err
	}

	return price, nil
}

// save the registration
func (r *Registerer) save(in *data.Person, price float64) (int, error) {
	person := &data.Person{
		FullName: in.FullName,
		Phone:    in.Phone,
		Currency: in.Currency,
		Price:    price,
	}
	return saver(person)
}

// this function as a variable allows us to Monkey Patch during testing
var saver = data.Save


================================================
FILE: ch05/acme/internal/modules/register/register_test.go
================================================
package register

import (
	"errors"
	"testing"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRegisterer_Do_happyPath(t *testing.T) {
	// monkey patch calls to the data package
	defer func(original func(in *data.Person) (int, error)) {
		// restore original
		saver = original
	}(saver)

	// replace method
	saver = func(in *data.Person) (int, error) {
		result := 888
		var resultErr error

		return result, resultErr
	}
	// end of monkey patch

	// inputs
	in := &data.Person{
		FullName: "Chang",
		Phone:    "11122233355",
		Currency: "CNY",
	}

	// call method
	registerer := &Registerer{}
	ID, err := registerer.Do(in)

	// validate expectations
	require.NoError(t, err)
	assert.Equal(t, 888, ID)
}

func TestRegisterer_Do_error(t *testing.T) {
	// monkey patch calls to the data package
	defer func(original func(in *data.Person) (int, error)) {
		// restore original
		saver = original
	}(saver)

	// replace method
	saver = func(in *data.Person) (int, error) {
		var result int
		resultErr := errors.New("failed to save")

		return result, resultErr
	}
	// end of monkey patch

	// inputs
	in := &data.Person{
		FullName: "Chang",
		Phone:    "11122233355",
		Currency: "CNY",
	}

	// call method
	registerer := &Registerer{}
	ID, err := registerer.Do(in)

	// validate expectations
	require.Error(t, err)
	assert.Equal(t, 0, ID)
}


================================================
FILE: ch05/acme/internal/rest/common_test.go
================================================
package rest

import (
	"context"
	"net"
)

func getOpenPort() (string, error) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return "", err
	}

	address := listener.Addr().String()
	listener.Close()

	return address, nil
}

func startServer(ctx context.Context) (string, error) {
	// get open port
	address, err := getOpenPort()
	if err != nil {
		return "", err
	}

	// start a server
	server := New(address)
	go server.Listen(ctx.Done())

	// wait for server to be ready
	dialer := &net.Dialer{}
	for {
		conn, _ := dialer.DialContext(ctx, "tcp", address)
		if conn != nil {
			defer conn.Close()

			return address, nil
		}

		select {
		case <-ctx.Done():
			return "", ctx.Err()

		default:
			// try again
		}
	}

	return address, nil
}


================================================
FILE: ch05/acme/internal/rest/get.go
================================================
package rest

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/get"
	"github.com/gorilla/mux"
)

const (
	// default person id (returned on error)
	defaultPersonID = 0
)

// GetHandler is the HTTP handler for the "Get Person" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400
// or "not found" HTTP 404
// There are some programmer errors possible but hopefully these will be caught in testing.
type GetHandler struct {
}

// ServeHTTP implements http.Handler
func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// extract person id from request
	id, err := h.extractID(request)
	if err != nil {
		// output error
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// attempt get
	getter := get.Getter{}
	person, err := getter.Do(id)
	if err != nil {
		// not need to log here as we can expect other layers to do so
		response.WriteHeader(http.StatusNotFound)
		return
	}

	// happy path
	err = h.writeJSON(response, person)
	if err != nil {
		// this error should not happen but if it does there is nothing we can do to recover
		response.WriteHeader(http.StatusInternalServerError)
	}
}

// extract the person ID from the request
func (h *GetHandler) extractID(request *http.Request) (int, error) {
	// ID is part of the URL, so we extract it from there
	vars := mux.Vars(request)
	idAsString, exists := vars["id"]
	if !exists {
		// log and return error
		err := errors.New("[get] person id missing from request")
		logging.L.Warn(err.Error())
		return defaultPersonID, err
	}

	// convert ID to int
	id, err := strconv.Atoi(idAsString)
	if err != nil {
		// log and return error
		err = fmt.Errorf("[get] failed to convert person id into a number. err: %s", err)
		logging.L.Error(err.Error())
		return defaultPersonID, err
	}

	return id, nil
}

// output the supplied person as JSON
func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) error {
	output := &getResponseFormat{
		ID:       person.ID,
		FullName: person.FullName,
		Phone:    person.Phone,
		Currency: person.Currency,
		Price:    person.Price,
	}

	// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
	return json.NewEncoder(writer).Encode(output)
}

// the JSON response format
type getResponseFormat struct {
	ID       int     `json:"id"`
	FullName string  `json:"name"`
	Phone    string  `json:"phone"`
	Currency string  `json:"currency"`
	Price    float64 `json:"price"`
}


================================================
FILE: ch05/acme/internal/rest/get_test.go
================================================
package rest

import (
	"context"
	"io/ioutil"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	response, err := http.Get("http://" + address + "/person/1/")

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.StatusCode)

	expectedPayload := []byte(`{"id":1,"name":"John","phone":"0123456780","currency":"USD","price":100}` + "\n")
	payload, _ := ioutil.ReadAll(response.Body)
	defer response.Body.Close()

	assert.Equal(t, expectedPayload, payload)
}


================================================
FILE: ch05/acme/internal/rest/list.go
================================================
package rest

import (
	"encoding/json"
	"io"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/list"
)

// ListHandler is the HTTP handler for the "List Do people" endpoint
// In this simplified example we are assuming all possible errors are system errors (HTTP 500)
type ListHandler struct {
}

// ServeHTTP implements http.Handler
func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// attempt loadAll
	lister := list.Lister{}
	people, err := lister.Do()
	if err != nil {
		// not need to log here as we can expect other layers to do so
		response.WriteHeader(http.StatusNotFound)
		return
	}

	// happy path
	err = h.writeJSON(response, people)
	if err != nil {
		// this error should not happen but if it does there is nothing we can do to recover
		response.WriteHeader(http.StatusInternalServerError)
	}
}

// output the result as JSON
func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Person) error {
	output := &listResponseFormat{
		People: make([]*listResponseItemFormat, len(people)),
	}

	for index, record := range people {
		output.People[index] = &listResponseItemFormat{
			ID:       record.ID,
			FullName: record.FullName,
			Phone:    record.Phone,
		}
	}

	// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
	return json.NewEncoder(writer).Encode(output)
}

type listResponseFormat struct {
	People []*listResponseItemFormat `json:"people"`
}

type listResponseItemFormat struct {
	ID       int    `json:"id"`
	FullName string `json:"name"`
	Phone    string `json:"phone"`
}


================================================
FILE: ch05/acme/internal/rest/list_test.go
================================================
package rest

import (
	"context"
	"io/ioutil"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestListHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	response, err := http.Get("http://" + address + "/person/list")

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.StatusCode)

	expectedPayload := []byte(`{"people":[{"id":1,"name":"John","phone":"0123456780"},{"id":2,"name":"Paul","phone":"0123456781"},{"id":3,"name":"George","phone":"0123456782"},{"id":4,"name":"Ringo","phone":"0123456783"}`)
	payload, _ := ioutil.ReadAll(response.Body)
	defer response.Body.Close()

	// we have to use contains because other tests add more records
	assert.Contains(t, string(payload), string(expectedPayload))
}


================================================
FILE: ch05/acme/internal/rest/not_found.go
================================================
package rest

import (
	"net/http"
)

func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
	response.WriteHeader(http.StatusNotFound)
	_, _ = response.Write([]byte(`Not found`))
}


================================================
FILE: ch05/acme/internal/rest/not_found_test.go
================================================
package rest

import (
	"context"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestNotFoundHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	response, err := http.Get("http://" + address + "/some-bad-address")

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusNotFound, response.StatusCode)
}


================================================
FILE: ch05/acme/internal/rest/register.go
================================================
package rest

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/register"
)

// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
}

// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// extract payload from request
	requestPayload, err := h.extractPayload(request)
	if err != nil {
		// output error
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// register person
	id, err := h.register(requestPayload)
	if err != nil {
		// not need to log here as we can expect other layers to do so
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// happy path
	response.Header().Add("Location", fmt.Sprintf("/person/%d/", id))
	response.WriteHeader(http.StatusCreated)
}

// extract payload from request
func (h *RegisterHandler) extractPayload(request *http.Request) (*registerRequest, error) {
	requestPayload := &registerRequest{}

	decoder := json.NewDecoder(request.Body)
	err := decoder.Decode(requestPayload)
	if err != nil {
		return nil, err
	}

	return requestPayload, nil
}

// call the logic layer
func (h *RegisterHandler) register(requestPayload *registerRequest) (int, error) {
	person := &data.Person{
		FullName: requestPayload.FullName,
		Phone:    requestPayload.Phone,
		Currency: requestPayload.Currency,
	}

	registerer := &register.Registerer{}
	return registerer.Do(person)
}

// register endpoint request format
type registerRequest struct {
	// FullName of the person
	FullName string `json:"fullName"`
	// Phone of the person
	Phone string `json:"phone"`
	// Currency the wish to register in
	Currency string `json:"currency"`
}


================================================
FILE: ch05/acme/internal/rest/register_test.go
================================================
package rest

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRegisterHandler_ServeHTTP(t *testing.T) {
	// ensure the test always fails by giving it a timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Create and start a server
	// With out current implementation, we cannot test this handler without a full server as we need the mux.
	address, err := startServer(ctx)
	require.NoError(t, err)

	// build inputs
	validRequest := buildValidRequest()
	response, err := http.Post("http://"+address+"/person/register", "application/json", validRequest)

	// validate outputs
	require.NoError(t, err)
	require.Equal(t, http.StatusCreated, response.StatusCode)
	defer response.Body.Close()

	// call should output the location to the new person
	headerLocation := response.Header.Get("Location")
	assert.Contains(t, headerLocation, "/person/")
}

func buildValidRequest() io.Reader {
	requestData := &registerRequest{
		FullName: "Joan Smith",
		Currency: "AUD",
		Phone:    "01234567890",
	}

	data, _ := json.Marshal(requestData)
	return bytes.NewBuffer(data)
}


================================================
FILE: ch05/acme/internal/rest/server.go
================================================
package rest

import (
	"net/http"

	"github.com/gorilla/mux"
)

// New will create and initialize the server
func New(address string) *Server {
	return &Server{
		address:         address,
		handlerGet:      &GetHandler{},
		handlerList:     &ListHandler{},
		handlerNotFound: notFoundHandler,
		handlerRegister: &RegisterHandler{},
	}
}

// Server is the HTTP REST server
type Server struct {
	address string
	server  *http.Server

	handlerGet      http.Handler
	handlerList     http.Handler
	handlerNotFound http.HandlerFunc
	handlerRegister http.Handler
}

// Listen will start a HTTP rest for this service
func (s *Server) Listen(stop <-chan struct{}) {
	router := s.buildRouter()

	// create the HTTP server
	s.server = &http.Server{
		Handler: router,
		Addr:    s.address,
	}

	// listen for shutdown
	go func() {
		// wait for shutdown signal
		<-stop

		_ = s.server.Close()
	}()

	// start the HTTP server
	_ = s.server.ListenAndServe()
}

// configure the endpoints to handlers
func (s *Server) buildRouter() http.Handler {
	router := mux.NewRouter()

	// map URL endpoints to HTTP handlers
	router.Handle("/person/{id}/", s.handlerGet).Methods("GET")
	router.Handle("/person/list", s.handlerList).Methods("GET")
	router.Handle("/person/register", s.handlerRegister).Methods("POST")

	// convert a "catch all" not found handler
	router.NotFoundHandler = s.handlerNotFound

	return router
}


================================================
FILE: ch05/acme/main.go
================================================
package main

import (
	"context"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/rest"
)

func main() {
	// bind stop channel to context
	ctx := context.Background()

	// start REST server
	server := rest.New(config.App.Address)
	server.Listen(ctx.Done())
}


================================================
FILE: ch05/fake.go
================================================
package ch05

func init() {
	// This file is included so that Go tools (like `go list`) will find Go code in this directory and not error
}


================================================
FILE: ch06/01_constructor_injection/01_welcome_email.go
================================================
package constructor_injection

import (
	"errors"
)

func NewWelcomeSender(in *Mailer) (*WelcomeSender, error) {
	// guard clause
	if in == nil {
		return nil, errors.New("programmer error: mailer must not provided")
	}

	return &WelcomeSender{
		mailer: in,
	}, nil
}

func NewWelcomeSenderNoGuard(in *Mailer) *WelcomeSender {
	return &WelcomeSender{
		mailer: in,
	}
}

// WelcomeSender sends a Welcome email to new users
type WelcomeSender struct {
	mailer *Mailer
}

func (w *WelcomeSender) Send(to string) error {
	body := w.buildMessage()

	return w.mailer.Send(to, body)
}

// build and return the message body
func (w *WelcomeSender) buildMessage() string {
	return ""
}

// Mailer sends and receives emails
type Mailer struct {
	Host     string
	Port     string
	Username string
	Password string
}

func (m *Mailer) Send(to string, body string) error {
	// send email
	return nil
}

func (m *Mailer) Receive(address string) (string, error) {
	// receive email
	return "", nil
}


================================================
FILE: ch06/01_constructor_injection/01_welcome_email_test.go
================================================
package constructor_injection

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewWelcomeSender_happyPath(t *testing.T) {
	sender, err := NewWelcomeSender(&Mailer{})
	assert.NotNil(t, sender)
	assert.NoError(t, err)
}

func TestNewWelcomeSender_guardClause(t *testing.T) {
	sender, err := NewWelcomeSender(nil)
	assert.Nil(t, sender)
	assert.Error(t, err)
}

func TestNewWelcomeSenderNoGuard_happyPath(t *testing.T) {
	sender := NewWelcomeSenderNoGuard(&Mailer{})
	assert.NotNil(t, sender)
}


================================================
FILE: ch06/01_constructor_injection/02_mailer_interface.go
================================================
package constructor_injection

// Mailer sends and receives emails
type MailerInterface interface {
	Send(to string, body string) error
	Receive(address string) (string, error)
}


================================================
FILE: ch06/01_constructor_injection/03_sender_interface.go
================================================
package constructor_injection

type Sender interface {
	Send(to string, body string) error
}

func NewWelcomeSenderV2(in Sender) *WelcomeSenderV2 {
	return &WelcomeSenderV2{
		sender: in,
	}
}

// WelcomeSenderV2 sends a Welcome email to new users
type WelcomeSenderV2 struct {
	sender Sender
}

func (w *WelcomeSenderV2) Send(to string) error {
	body := w.buildMessage()

	return w.sender.Send(to, body)
}

// build and return the message body
func (w *WelcomeSenderV2) buildMessage() string {
	return ""
}


================================================
FILE: ch06/01_constructor_injection/05_duck_typing.go
================================================
package constructor_injection

import (
	"fmt"
)

type Talker interface {
	Speak() string
	Shout() string
}

type Dog struct{}

func (d Dog) Speak() string {
	return "Woof!"
}

func (d Dog) Shout() string {
	return "WOOF!"
}

func SpeakExample() {
	var talker Talker
	talker = Dog{}

	fmt.Print(talker.Speak())
}


================================================
FILE: ch06/02_advantages/01_easy_to_implement.go
================================================
package advantages

// WelcomeSender sends a Welcome email to new users
type WelcomeSender struct {
	Mailer *Mailer
}

func (w *WelcomeSender) Send(to string) error {
	body := w.buildMessage()

	return w.Mailer.Send(to, body)
}

// build and return the message body
func (w *WelcomeSender) buildMessage() string {
	return ""
}

// Mailer will send an email
type Mailer struct{}

func (m *Mailer) Send(to string, body string) error {
	// send email
	return nil
}


================================================
FILE: ch06/02_advantages/01_easy_to_implement_example_test.go
================================================
package advantages_test

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages"
)

func ExampleWelcomeSender_Send() {
	welcomeSender := &advantages.WelcomeSender{
		Mailer: &advantages.Mailer{},
	}
	welcomeSender.Send("me@home.com")
}


================================================
FILE: ch06/02_advantages/02_easy_to_implement.go
================================================
package advantages

func NewWelcomeSenderV2(mailer *Mailer) *WelcomeSenderV2 {
	return &WelcomeSenderV2{
		mailer: mailer,
	}
}

// WelcomeSenderV2 sends a Welcome email to new users
type WelcomeSenderV2 struct {
	mailer *Mailer
}

func (w *WelcomeSenderV2) Send(to string) error {
	body := w.buildMessage()

	return w.mailer.Send(to, body)
}

// build and return the message body
func (w *WelcomeSenderV2) buildMessage() string {
	return ""
}


================================================
FILE: ch06/02_advantages/02_easy_to_implement_example_test.go
================================================
package advantages_test

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages"
)

func ExampleWelcomeSenderV2_Send() {
	welcomeSender := advantages.NewWelcomeSenderV2(&advantages.Mailer{})
	welcomeSender.Send("me@home.com")
}


================================================
FILE: ch06/02_advantages/03_predictable.go
================================================
package advantages

import (
	"errors"
)

type Engine interface {
	Start()
	IncreasePower()
	DecreasePower()
	Stop()
	IsRunning() bool
}

type Car struct {
	Engine Engine
}

func (c *Car) Drive() error {
	if c.Engine == nil {
		return errors.New("engine ie missing")
	}

	// use the engine
	c.Engine.Start()
	c.Engine.IncreasePower()

	return nil
}

func (c *Car) Stop() error {
	if c.Engine == nil {
		return errors.New("engine ie missing")
	}

	// use the engine
	c.Engine.DecreasePower()
	c.Engine.Stop()

	return nil
}


================================================
FILE: ch06/02_advantages/04_predictable.go
================================================
package advantages

import (
	"errors"
)

func NewCarV2(engine Engine) (*CarV2, error) {
	if engine == nil {
		return nil, errors.New("invalid engine supplied")
	}

	return &CarV2{
		engine: engine,
	}, nil
}

type CarV2 struct {
	engine Engine
}

func (c *CarV2) Drive() error {
	// use the engine
	c.engine.Start()
	c.engine.IncreasePower()

	return nil
}

func (c *CarV2) Stop() error {
	// use the engine
	c.engine.DecreasePower()
	c.engine.Stop()

	return nil
}


================================================
FILE: ch06/02_advantages/05_encapsulation.go
================================================
package advantages

import (
	"errors"
)

func (c *CarV2) FillPetrolTank() error {
	// use the engine
	if c.engine.IsRunning() {
		return errors.New("cannot fill the tank while the engine is running")
	}

	// fill the tank!
	return c.fill()
}

func (c CarV2) fill() error {
	// TODO: implement
	return nil
}


================================================
FILE: ch06/02_advantages/06_encapsulation.go
================================================
package advantages

import (
	"errors"
)

func (c *CarV2) FillPetrolTankV2(engine Engine) error {
	// use the engine
	if engine.IsRunning() {
		return errors.New("cannot fill the tank while the engine is running")
	}

	// fill the tank!
	return c.fill()
}


================================================
FILE: ch06/03_applying/01/01_register_handler_before.go
================================================
package rest

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/register"
)

// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
}

// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// extract payload from request
	requestPayload, err := h.extractPayload(request)
	if err != nil {
		// output error
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// register person
	id, err := h.register(requestPayload)
	if err != nil {
		// not need to log here as we can expect other layers to do so
		response.WriteHeader(http.StatusBadRequest)
		return
	}

	// happy path
	response.Header().Add("Location", fmt.Sprintf("/person/%d/", id))
	response.WriteHeader(http.StatusCreated)
}

// extract payload from request
func (h *RegisterHandler) extractPayload(request *http.Request) (*registerRequest, error) {
	requestPayload := &registerRequest{}

	decoder := json.NewDecoder(request.Body)
	err := decoder.Decode(requestPayload)
	if err != nil {
		return nil, err
	}

	return requestPayload, nil
}

// call the logic layer
func (h *RegisterHandler) register(requestPayload *registerRequest) (int, error) {
	person := &data.Person{
		FullName: requestPayload.FullName,
		Phone:    requestPayload.Phone,
		Currency: requestPayload.Currency,
	}

	registerer := &register.Registerer{}
	return registerer.Do(person)
}

// register endpoint request format
type registerRequest struct {
	// FullName of the person
	FullName string `json:"fullName"`
	// Phone of the person
	Phone string `json:"phone"`
	// Currency the wish to register in
	Currency string `json:"currency"`
}


================================================
FILE: ch06/03_applying/01/data/person.go
================================================
package data

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}


================================================
FILE: ch06/03_applying/01/register/register.go
================================================
package register

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
)

// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}

// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
	// fake implementation
	return 0, nil
}


================================================
FILE: ch06/03_applying/02/01_register_handler.go
================================================
package rest

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/02/register"
)

// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
	registerer *register.Registerer
}


================================================
FILE: ch06/03_applying/02/data/person.go
================================================
package data

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}


================================================
FILE: ch06/03_applying/02/register/register.go
================================================
package register

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
)

// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}

// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
	// fake implementation
	return 0, nil
}


================================================
FILE: ch06/03_applying/03/data/person.go
================================================
package data

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}


================================================
FILE: ch06/03_applying/03/mock_register_model_test.go
================================================
// Code generated by mockery v1.0.0

// @generated

package rest

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/03/data"
	"github.com/stretchr/testify/mock"
)

// MockRegisterModel is an autogenerated mock type for the RegisterModel type
type MockRegisterModel struct {
	mock.Mock
}

// Do provides a mock function with given fields: in
func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {
	ret := _m.Called(in)

	var r0 int
	if rf, ok := ret.Get(0).(func(*data.Person) int); ok {
		r0 = rf(in)
	} else {
		r0 = ret.Get(0).(int)
	}

	var r1 error
	if rf, ok := ret.Get(1).(func(*data.Person) error); ok {
		r1 = rf(in)
	} else {
		r1 = ret.Error(1)
	}

	return r0, r1
}


================================================
FILE: ch06/03_applying/03/register_test.go
================================================
package rest

import (
	"net/http"
	"testing"
)

func TestRegisterHandler_ServeHTTP(t *testing.T) {
	scenarios := []struct {
		desc           string
		inRequest      func() *http.Request
		inModelMock    func() *MockRegisterModel
		expectedStatus int
		expectedHeader string
	}{
		// scenarios go here
	}

	for _, s := range scenarios {
		scenario := s
		t.Run(scenario.desc, func(t *testing.T) {
			// test goes here
		})
	}
}


================================================
FILE: ch06/03_applying/04/data/person.go
================================================
package data

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}


================================================
FILE: ch06/03_applying/04/mock_register_model_test.go
================================================
// Code generated by mockery v1.0.0

// @generated

package rest

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/04/data"
	"github.com/stretchr/testify/mock"
)

// MockRegisterModel is an autogenerated mock type for the RegisterModel type
type MockRegisterModel struct {
	mock.Mock
}

// Do provides a mock function with given fields: in
func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {
	ret := _m.Called(in)

	var r0 int
	if rf, ok := ret.Get(0).(func(*data.Person) int); ok {
		r0 = rf(in)
	} else {
		r0 = ret.Get(0).(int)
	}

	var r1 error
	if rf, ok := ret.Get(1).(func(*data.Person) error); ok {
		r1 = rf(in)
	} else {
		r1 = ret.Error(1)
	}

	return r0, r1
}


================================================
FILE: ch06/03_applying/04/register.go
================================================
package rest

import (
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/04/data"
)

// RegisterModel will validate and save a registration
type RegisterModel interface {
	Do(in *data.Person) (int, error)
}

// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
	registerer RegisterModel
}

// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	// implementation goes here
}

// register endpoint request format
type registerRequest struct {
	// FullName of the person
	FullName string `json:"fullName"`
	// Phone of the person
	Phone string `json:"phone"`
	// Currency the wish to register in
	Currency string `json:"currency"`
}


================================================
FILE: ch06/03_applying/04/register_test.go
================================================
package rest

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRegisterHandler_ServeHTTP(t *testing.T) {
	scenarios := []struct {
		desc           string
		inRequest      func() *http.Request
		inModelMock    func() *MockRegisterModel
		expectedStatus int
		expectedHeader string
	}{
		// scenarios go here
	}

	for _, s := range scenarios {
		scenario := s
		t.Run(scenario.desc, func(t *testing.T) {
			// define model layer mock
			mockRegisterModel := scenario.inModelMock()

			// build handler
			handler := &RegisterHandler{
				registerer: mockRegisterModel,
			}

			// perform request
			response := httptest.NewRecorder()
			handler.ServeHTTP(response, scenario.inRequest())

			// validate outputs
			require.Equal(t, scenario.expectedStatus, response.Code)

			// call should output the location to the new person
			resultHeader := response.Header().Get("Location")
			assert.Equal(t, scenario.expectedHeader, resultHeader)

			// validate the mock was used as we expected
			assert.True(t, mockRegisterModel.AssertExpectations(t))
		})
	}
}

func buildValidRequest() io.Reader {
	requestData := &registerRequest{
		FullName: "Joan Smith",
		Currency: "AUD",
		Phone:    "01234567890",
	}

	data, _ := json.Marshal(requestData)
	return bytes.NewBuffer(data)
}


================================================
FILE: ch06/03_applying/05/data/person.go
================================================
package data

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}


================================================
FILE: ch06/03_applying/05/fakes.go
================================================
package reset

import (
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)

func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
	response.WriteHeader(http.StatusNotFound)
	_, _ = response.Write([]byte(`Not found`))
}

// Fake/Stub implementations to make the compiler happy

type Server struct {
	address string

	handlerGet      http.Handler
	handlerList     http.Handler
	handlerNotFound http.HandlerFunc
	handlerRegister http.Handler
}

func NewGetHandler(_ GetModel) *GetHandler {
	return &GetHandler{}
}

type GetModel interface {
	Do(ID int) (*data.Person, error)
}

type GetHandler struct{}

func (g *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {}

func NewListHandler(_ ListModel) *ListHandler {
	return &ListHandler{}
}

type ListModel interface {
	Do() ([]*data.Person, error)
}

type ListHandler struct {
}

func (l *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {}

func NewRegisterHandler(_ RegisterModel) *RegisterHandler {
	return &RegisterHandler{}
}

type RegisterModel interface {
	Do(in *data.Person) (int, error)
}

type RegisterHandler struct {
}

func (r *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {}


================================================
FILE: ch06/03_applying/05/get/getter.go
================================================
package get

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)

// Stub implementation so that the example compiles
type Getter struct{}

func (g *Getter) Do(ID int) (*data.Person, error) {
	return nil, nil
}


================================================
FILE: ch06/03_applying/05/list/lister.go
================================================
package list

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)

// Stub implementation so that the example compiles
type Lister struct{}

func (l *Lister) Do() ([]*data.Person, error) {
	return nil, nil
}


================================================
FILE: ch06/03_applying/05/register/registerer.go
================================================
package register

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)

// Stub implementation so that the example compiles
type Registerer struct{}

func (r *Registerer) Do(in *data.Person) (int, error) {
	return 0, nil
}


================================================
FILE: ch06/03_applying/05/server.go
================================================
package reset

import (
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/get"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/list"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/register"
)

// New will create and initialize the server
func New(address string) *Server {
	return &Server{
		address:         address,
		handlerGet:      NewGetHandler(&get.Getter{}),
		handlerList:     NewListHandler(&list.Lister{}),
		handlerNotFound: notFoundHandler,
		handlerRegister: NewRegisterHandler(&register.Registerer{}),
	}
}


================================================
FILE: ch06/04_disadvantages/01_lots_of_changes.go
================================================
package disadvantages

// Dealer will shuffle a deck of cards and deal them to the players
func DealCards() (player1 []Card, player2 []Card) {
	// create a new deck of cards
	cards := newDeck()

	// shuffle the cards
	shuffler := &myShuffler{}
	shuffler.Shuffle(cards)

	// deal
	player1 = append(player1, cards[0])
	player2 = append(player2, cards[1])

	player1 = append(player1, cards[2])
	player2 = append(player2, cards[3])
	return
}

// returns a new deck of cards
func newDeck() []Card {
	return []Card{
		// code removed
	}
}

// Shuffler will shuffle (randomize) the supplied cards
type Shuffler interface {
	Shuffle(cards []Card)
}

// Card is single Playing Card
type Card struct {
	Suit  string
	Value string
}

// implements Shuffler
type myShuffler struct{}

// Shuffle implements shuffler
func (m *myShuffler) Shuffle(cards []Card) {
	// randomize the cards
}


================================================
FILE: ch06/04_disadvantages/02_overuse.go
================================================
package disadvantages

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
)

const downstreamServer = "http://www.example.com"

// FetchRates rates from downstream service
type FetchRates struct{}

func (f *FetchRates) Fetch() ([]Rate, error) {
	// build the URL from which to fetch the rates
	url := downstreamServer + "/rates"

	// build request
	request, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	// fetch rates
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	// read the content of the response
	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	// convert JSON bytes to Go structs
	out := &downstreamResponse{}
	err = json.Unmarshal(data, out)
	if err != nil {
		return nil, err
	}

	return out.Rates, nil
}

// response format from the downstream service
type downstreamResponse struct {
	Rates []Rate `json:"rates"`
}

type Rate struct {
	Code  string
	Value float64
}


================================================
FILE: ch06/04_disadvantages/03_non_obvious.go
================================================
package disadvantages

import (
	"errors"
)

// NewClient creates and initialises the client
func NewClient(service DepService) Client {
	return &clientImpl{
		service: service,
	}
}

// Client is the exported API
type Client interface {
	DoSomethingUseful() (bool, error)
}

// implement Client
type clientImpl struct {
	service DepService
}

func (c *clientImpl) DoSomethingUseful() (bool, error) {
	// this function does something useful
	return false, errors.New("not implemented")
}

type DepService interface {
	DoSomethingElse()
}


================================================
FILE: ch06/04_disadvantages/04_non_obvious_example_test.go
================================================
package disadvantages_test

func Example() {

}

// StubClient is a stub implementation of disadvantages.Client interface
type StubClient struct{}

// DoSomethingUseful implements disadvantages.Client
func (s *StubClient) DoSomethingUseful() (bool, error) {
	return true, nil
}


================================================
FILE: ch06/04_disadvantages/05_constructors.go
================================================
package disadvantages

type InnerService struct {
	innerDep Dependency
}

func NewInnerService(innerDep Dependency) *InnerService {
	return &InnerService{
		innerDep: innerDep,
	}
}

type OuterService struct {
	// composition
	innerService *InnerService

	outerDep Dependency
}

func NewOuterService(outerDep Dependency, innerDep Dependency) *OuterService {
	return &OuterService{
		innerService: NewInnerService(innerDep),
		outerDep:     outerDep,
	}
}

// fake type to satisfy the compiler
type Dependency interface {
}


================================================
FILE: ch06/acme/internal/config/config.go
================================================
package config

import (
	"encoding/json"
	"io/ioutil"
	"os"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/logging"
)

// DefaultEnvVar is the default environment variable the points to the config file
const DefaultEnvVar = "ACME_CONFIG"

// App is the application config
var App *Config

// Config defines the JSON format for the config file
type Config struct {
	// DSN is the data source name (format: https://github.com/go-sql-driver/mysql/#dsn-data-source-name)
	DSN string

	// Address is the IP address and port to bind this rest to
	Address string

	// BasePrice is the price of registration
	BasePrice float64

	// ExchangeRateBaseURL is the server and protocol part of the URL from which to load the exchange rate
	ExchangeRateBaseURL string

	// ExchangeRateAPIKey is the API for the exchange rate API
	ExchangeRateAPIKey string
}

// Load returns the config loaded from environment
func init() {
	filename, found := os.LookupEnv(DefaultEnvVar)
	if !found {
		logging.L.Error("failed to locate file specified by %s", DefaultEnvVar)
		return
	}

	_ = load(filename)
}

func load(filename string) error {
	App = &Config{}
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		logging.L.Error("failed to read config file. err: %s", err)
		return err
	}

	err = json.Unmarshal(bytes, App)
	if err != nil {
		logging.L.Error("failed to parse config file. err : %s", err)
		return err
	}

	return nil
}


================================================
FILE: ch06/acme/internal/config/config_test.go
================================================
package config

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLoad(t *testing.T) {
	scenarios := []struct {
		desc           string
		in             string
		expectedConfig *Config
		expectError    bool
	}{
		{
			desc: "happy path",
			in:   "../../../../default-config.json",
			expectedConfig: &Config{
				DSN:                 "[insert your db config here]",
				Address:             "0.0.0.0:8080",
				BasePrice:           100.00,
				ExchangeRateBaseURL: "http://apilayer.net",
				ExchangeRateAPIKey:  "[insert your API key here]",
			},
			expectError: false,
		},
		{
			desc:           "invalid path",
			in:             "invalid.json",
			expectedConfig: &Config{},
			expectError:    true,
		},
	}

	for _, s := range scenarios {
		scenario := s
		t.Run(scenario.desc, func(t *testing.T) {
			resultErr := load(scenario.in)
			require.Equal(t, scenario.expectError, resultErr != nil, "err: %s", resultErr)
			assert.Equal(t, scenario.expectedConfig, App, scenario.desc)
		})
	}

}


================================================
FILE: ch06/acme/internal/logging/logging.go
================================================
package logging

import (
	"fmt"
)

// L is the global instance of the logger
var L = &LoggerStdOut{}

// LoggerStdOut logs to std out
type LoggerStdOut struct{}

// Debug logs messages at DEBUG level
func (l LoggerStdOut) Debug(message string, args ...interface{}) {
	fmt.Printf("[DEBUG] "+message, args...)
}

// Info logs messages at INFO level
func (l LoggerStdOut) Info(message string, args ...interface{}) {
	fmt.Printf("[INFO] "+message, args...)
}

// Warn logs messages at WARN level
func (l LoggerStdOut) Warn(message string, args ...interface{}) {
	fmt.Printf("[WARN] "+message, args...)
}

// Error logs messages at ERROR level
func (l LoggerStdOut) Error(message string, args ...interface{}) {
	fmt.Printf("[ERROR] "+message, args...)
}


================================================
FILE: ch06/acme/internal/modules/data/data.go
================================================
package data

import (
	"database/sql"
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/logging"
	// import the MySQL Driver
	_ "github.com/go-sql-driver/mysql"
)

const (
	// default person id (returned on error)
	defaultPersonID = 0

	// SQL statements as constants (to reduce duplication and maintenance in tests)
	sqlAllColumns = "id, fullname, phone, currency, price"
	sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
	sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
	sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? LIMIT 1"
)

var (
	db *sql.DB

	// ErrNotFound is returned when the no records where matched by the query
	ErrNotFound = errors.New("not found")
)

var getDB = func() (*sql.DB, error) {
	if db == nil {
		if config.App == nil {
			return nil, errors.New("config is not initialized")
		}

		var err error
		db, err = sql.Open("mysql", config.App.DSN)
		if err != nil {
			// if the DB cannot be accessed we are dead
			panic(err.Error())
		}
	}

	return db, nil
}

// Person is the data transfer object (DTO) for this package
type Person struct {
	// ID is the unique ID for this person
	ID int
	// FullName is the name of this person
	FullName string
	// Phone is the phone for this person
	Phone string
	// Currency is the currency this person has paid in
	Currency string
	// Price is the amount (in the above currency) paid by this person
	Price float64
}

// Save will save the supplied person and return the ID of the newly created person or an error.
// Errors returned are caused by the underlying database or our connection to it.
func Save(in *Person) (int, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return defaultPersonID, err
	}

	// perform DB insert
	result, err := db.Exec(sqlInsert, in.FullName, in.Phone, in.Currency, in.Price)
	if err != nil {
		logging.L.Error("failed to save person into DB. err: %s", err)
		return defaultPersonID, err
	}

	// retrieve and return the ID of the person created
	id, err := result.LastInsertId()
	if err != nil {
		logging.L.Error("failed to retrieve id of last saved person. err: %s", err)
		return defaultPersonID, err
	}

	return int(id), nil
}

// LoadAll will attempt to load all people in the database
// It will return ErrNotFound when there are not people in the database
// Any other errors returned are caused by the underlying database or our connection to it.
func LoadAll() ([]*Person, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return nil, err
	}

	// perform DB select
	rows, err := db.Query(sqlLoadAll)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = rows.Close()
	}()

	var out []*Person

	for rows.Next() {
		// retrieve columns and populate the person object
		record, err := populatePerson(rows.Scan)
		if err != nil {
			logging.L.Error("failed to convert query result. err: %s", err)
			return nil, err
		}

		out = append(out, record)
	}

	if len(out) == 0 {
		logging.L.Warn("no people found in the database.")
		return nil, ErrNotFound
	}

	return out, nil
}

// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
	db, err := getDB()
	if err != nil {
		logging.L.Error("failed to get DB connection. err: %s", err)
		return nil, err
	}

	// perform DB select
	row := db.QueryRow(sqlLoadByID, ID)

	// retrieve columns and populate the person object
	out, err := populatePerson(row.Scan)
	if err != nil {
		if err == sql.ErrNoRows {
			logging.L.Warn("failed to load requested person '%d'. err: %s", ID, err)
			return nil, ErrNotFound
		}

		logging.L.Error("failed to convert query result. err: %s", err)
		return nil, err
	}
	return out, nil
}

// custom type so we can convert sql results to easily
type scanner func(dest ...interface{}) error

// reduce the duplication (and maintenance) between sql.Row and sql.Rows usage
func populatePerson(scanner scanner) (*Person, error) {
	out := &Person{}
	err := scanner(&out.ID, &out.FullName, &out.Phone, &out.Currency, &out.Price)
	return out, err
}

func init() {
	// ensure the config is loaded and the db initialized
	_, _ = getDB()
}


================================================
FILE: ch06/acme/internal/modules/data/data_test.go
================================================
package data

import (
	"database/sql"
	"errors"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestSave_happyPath(t *testing.T) {
	// define a mock db
	testDb, dbMock, err := sqlmock.New()
	defer testDb.Close()
	require.NoError(t, err)

	// configure the mock db
	queryRegex := convertSQLToRegex(sqlInsert)
	dbMock.ExpectExec(queryRegex).WillReturnResult(sqlmock.NewResult(2, 1))

	// monkey patching starts here
	defer func(original sql.DB) {
		// restore original DB (after test)
		db = &original
	}(*db)

	// replace db for this test
	db = testDb
	// end of monkey patch

	// inputs
	in := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// call function
	resultID, err := Save(in)

	// validate result
	require.NoError(t, err)
	assert.Equal(t, 2, resultID)
	assert.NoError(t, dbMock.ExpectationsWereMet())
}

func TestSave_insertError(t *testing.T) {
	// define a mock db
	testDb, dbMock, err := sqlmock.New()
	defer testDb.Close()

	require.NoError(t, err)

	// configure the mock db
	queryRegex := convertSQLToRegex(sqlInsert)
	dbMock.ExpectExec(queryRegex).WillReturnError(errors.New("failed to insert"))

	// monkey patching starts here
	defer func(original sql.DB) {
		// restore original DB (after test)
		db = &original
	}(*db)

	// replace db for this test
	db = testDb
	// end of monkey patch

	// inputs
	in := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// call function
	resultID, err := Save(in)

	// validate result
	require.Error(t, err)
	assert.Equal(t, defaultPersonID, resultID)
	assert.NoError(t, dbMock.ExpectationsWereMet())
}

func TestSave_getDBError(t *testing.T) {
	// monkey patching starts here
	defer func(original func() (*sql.DB, error)) {
		// restore original DB (after test)
		getDB = original
	}(getDB)

	// replace getDB() function for this test
	getDB = func() (*sql.DB, error) {
		return nil, errors.New("getDB() failed")
	}
	// end of monkey patch

	// inputs
	in := &Person{
		FullName: "Jake Blues",
		Phone:    "01234567890",
		Currency: "AUD",
		Price:    123.45,
	}

	// call function
	resultID, err := Save(in)
	require.Error(t, err)
	assert.Equal(t, defaultPersonID, resultID)
}

func TestLoadAll_tableDrivenTest(t *testing.T) {
	scenarios := []struct {
		desc            string
		configureMockDB func(sqlmock.Sqlmock)
		expectedResults []*Person
		expectError     bool
	}{
		{
			desc: "happy path",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnRows(
					sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
						AddRow(1, "John", "0123456789", "AUD", 12.34))
			},
			expectedResults: []*Person{
				{
					ID:       1,
					FullName: "John",
					Phone:    "0123456789",
					Currency: "AUD",
					Price:    12.34,
				},
			},
			expectError: false,
		},
		{
			desc: "load error",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
			},
			expectedResults: nil,
			expectError:     true,
		},
	}

	for _, scenario := range scenarios {
		// define a mock db
		testDb, dbMock, err := sqlmock.New()
		require.NoError(t, err)

		// configure the mock db
		scenario.configureMockDB(dbMock)

		// monkey patch the db for this test
		original := *db
		db = testDb

		// call function
		results, err := LoadAll()

		// validate results
		assert.Equal(t, scenario.expectedResults, results, scenario.desc)
		assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
		assert.NoError(t, dbMock.ExpectationsWereMet())

		// restore original DB (after test)
		db = &original
		testDb.Close()
	}
}

func TestLoad_tableDrivenTest(t *testing.T) {
	scenarios := []struct {
		desc            string
		configureMockDB func(sqlmock.Sqlmock)
		expectedResult  *Person
		expectError     bool
	}{
		{
			desc: "happy path",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnRows(
					sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
						AddRow(2, "Paul", "0123456789", "CAD", 23.45))
			},
			expectedResult: &Person{
				ID:       2,
				FullName: "Paul",
				Phone:    "0123456789",
				Currency: "CAD",
				Price:    23.45,
			},
			expectError: false,
		},
		{
			desc: "load error",
			configureMockDB: func(dbMock sqlmock.Sqlmock) {
				queryRegex := convertSQLToRegex(sqlLoadAll)
				dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
			},
			expectedResult: nil,
			expectError:    true,
		},
	}

	for _, scenario := range scenarios {
		// define a mock db
		testDb, dbMock, err := sqlmock.New()
		require.NoError(t, err)

		// configure the mock db
		scenario.configureMockDB(dbMock)

		// monkey db for this test
		original := *db
		db = testDb

		// call function
		result, err := Load(2)

		// validate results
		assert.Equal(t, scenario.expectedResult, result, scenario.desc)
		assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
		assert.NoError(t, dbMock.ExpectationsWereMet())

		// restore original DB (after test)
		db = &original
		testDb.Close()
	}
}

// convert SQL string to regex by treating the entire query as a literal
func convertSQLToRegex(in string) string {
	return `\Q` + in + `\E`
}


================================================
FILE: ch06/acme/internal/modules/exchange/converter.go
================================================
package exchange

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/config"
	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/logging"
)

const (
	// request URL for the exchange rate API
	urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"

	// default price that is sent when an error occurs
	defaultPrice = 0.0
)

// Converter will convert the base price to the currency supplied
// Note: we are expecting sane inputs and therefore skipping input validation
type Converter struct{}

// Do will perform the conversion
func (c *Converter) Do(basePrice float64, currency string) (float64, error) {
	// load rate from the external API
	response, err := c.loadRateFromServer(currency)
	if err != nil {
		return defaultPrice, err
	}

	// extract rate from response
	rate, err := c.extractRate(response, currency)
	if err != nil {
		return defaultPrice, err
	}

	// apply rate and round to 2 decimal places
	return math.Floor((basePrice/rate)*100) / 100, nil
}

// load rate from the external API
func (c *Converter) loadRateFromServer(currency string) (*http.Response, error) {
	// build the request
	url := fmt.Sprintf(urlFormat,
		config.App.ExchangeRateBaseURL,
		config.App.ExchangeRateAPIKey,
		currency)

	// perform request
	response, err := http.Get(url)
	if err != nil {
		logging.L.Warn("[exchange] failed to load. err: %s", err)
		return nil, err
	}

	if response.StatusCode != http.StatusOK {
		err = fmt.Errorf("request failed with code %d", response.StatusCode)
		logging.L.Warn("[exchange] %s", err)
		return nil, err
	}

	return response, nil
}

func (c *Converter) extractRate(response *http.Response, currency string) (float64, error) {
	defer func() {
		_ = response.Body.Close()
	}()

	// extract data from response
	data, err := c.extractResponse(response)
	if err != nil {
		return defaultPrice, err
	}

	// pull rate from response data
	rate, found := data.Quotes["USD"+currency]
	if !found {
		err = fmt.Errorf("response did not include expected currency '%s'", currency)
		logging.L.Error("[exchange] %s", err)
		return defaultPrice, err
	}

	// happy path
	return rate, nil
}

func (c *Converter) extractResponse(response *http.Response) (*apiResponseFormat, error) {
	payload, err := ioutil.ReadAll(response.Body)
	if err != nil {
		logging.L.Error("[exchange] failed to ready response body. err: %s", err)
		return nil, err
	}

	data := &apiResponseFormat{}
	err = json.Unmarshal(payload, data)
	if err != nil {
		logging.L.Error("[exchange] error converting response. err: %s", err)
		return nil, err
	}

	// happy path
	return data, nil
}

// the response format from the exchange rate API
type apiResponseFormat struct {
	Quotes map[string]float64 `json:"quotes"`
}


================================================
FILE: ch06/acme/internal/modules/get/get.go
================================================
package get

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
)

var (
	// error thrown when the requested person is not in the database
	errPersonNotFound = errors.New("person not found")
)

// Getter will attempt to load a person.
// It can return an error caused by the data layer or when the requested person is not found
type Getter struct {
}

// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
	// load person from the data layer
	person, err := loader(ID)
	if err != nil {
		if err == data.ErrNotFound {
			// By converting the error we are hiding the implementation details from our users.
			return nil, errPersonNotFound
		}
		return nil, err
	}

	return person, err
}

// this function as a variable allows us to Monkey Patch during testing
var loader = data.Load


================================================
FILE: ch06/acme/internal/modules/get/go_test.go
================================================
package get

import (
	"errors"
	"testing"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetter_Do_happyPath(t *testing.T) {
	// inputs
	ID := 1234

	// monkey patch calls to the data package
	defer func(original func(ID int) (*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func(ID int) (*data.Person, error) {
		result := &data.Person{
			ID:       1234,
			FullName: "Doug",
		}
		var resultErr error

		return result, resultErr
	}
	// end of monkey patch

	// call method
	getter := &Getter{}
	person, err := getter.Do(ID)

	// validate expectations
	require.NoError(t, err)
	assert.Equal(t, ID, person.ID)
	assert.Equal(t, "Doug", person.FullName)
}

func TestGetter_Do_noSuchPerson(t *testing.T) {
	// inputs
	ID := 5678

	// monkey patch calls to the data package
	defer func(original func(ID int) (*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func(ID int) (*data.Person, error) {
		var result *data.Person
		resultErr := data.ErrNotFound

		return result, resultErr
	}
	// end of monkey patch

	// call method
	getter := &Getter{}
	person, err := getter.Do(ID)

	// validate expectations
	require.Equal(t, errPersonNotFound, err)
	assert.Nil(t, person)
}

func TestGetter_Do_error(t *testing.T) {
	// inputs
	ID := 1234

	// monkey patch calls to the data package
	defer func(original func(ID int) (*data.Person, error)) {
		// restore original
		loader = original
	}(loader)

	// replace method
	loader = func(ID int) (*data.Person, error) {
		var result *data.Person
		resultErr := errors.New("failed to load person")

		return result, resultErr
	}
	// end of monkey patch

	// call method
	getter := &Getter{}
	person, err := getter.Do(ID)

	// validate expectations
	require.Error(t, err)
	assert.Nil(t, person)
}


================================================
FILE: ch06/acme/internal/modules/list/list.go
================================================
package list

import (
	"errors"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
)

var (
	// error thrown when there are no people in the database
	errPeopleNotFound = errors.New("no people found")
)

// Lister will attempt to load all people in the database.
// It can return an error caused by the data layer
type Lister struct {
}

// Do will load the people from the data layer
func (l *Lister) Do() ([]*data.Person, error) {
	// load all people
	people, err := l.load()
	if err != nil {
		return nil, err
	}

	if len(people) == 0 {
		// special processing for 0 people returned
		return nil, errPeopleNotFound
	}

	return people, nil
}

// load all people
func (l *Lister) load() ([]*data.Person, error) {
	people, err := loader()
	if err != nil {
		if err == data.ErrNotFound {
			// By converting the error we are encapsulating the implementation details from our users.
			return nil, errPeopleNotFound
		}
		return nil, err
	}

	return people, nil
}

// this function as a variable allows us to Monkey Patch during testing
var loader = data.LoadAll


================================================
FILE: ch06/acme/internal/modules/list/list_test.go
================================================
package list

import (
	"errors"
	"testing"

	"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLister_Do_happyPath(t *testing.T) {
	// monkey 
Download .txt
gitextract_t7lucnow/

├── .gitignore
├── LICENSE
├── README.md
├── ch01/
│   ├── 01_defining_depenency_injection/
│   │   ├── 01_interface.go
│   │   ├── 02_function_literal.go
│   │   ├── 03_test_without_nfs_test.go
│   │   └── 04_fail_test_without_nfs_test.go
│   └── 02_code_smells/
│       ├── 01_code_bloat/
│       │   └── 01_switch_type.go
│       ├── 02_resistance_to_change/
│       │   └── 01_shotgun_surgey.go
│       ├── 03_wasted_effort/
│       │   ├── 01_excessive_comments.go
│       │   └── 02_complicated_go.go
│       └── 04_tight_coupling/
│           ├── 01_circular_dependencies/
│           │   ├── config/
│           │   │   └── config.go
│           │   └── payment/
│           │       └── currency.go
│           ├── 02_object_orgy.go
│           └── 03_feature_envy.go
├── ch02/
│   ├── 01_single_responsibility_principle/
│   │   ├── 01_responsibility_vs_change.go
│   │   ├── 02_responsibility_vs_change.go
│   │   ├── 03_responsibility_vs_change.go
│   │   ├── 04_long_method.go
│   │   ├── 04_long_method_test.go
│   │   └── 05_srp_method.go
│   ├── 02_open_closed_principle/
│   │   ├── 01_open_closed_failure.go
│   │   ├── 02_open_closed_success.go
│   │   ├── 03_shotgun_surgery.go
│   │   ├── 04_after_shotgun_surgery.go
│   │   ├── 05_composition.go
│   │   ├── 06_handler_struct.go
│   │   └── 07_handler_func.go
│   ├── 03_liskov_substitution_principle/
│   │   ├── 01_violation/
│   │   │   └── example.go
│   │   ├── 02_fixed/
│   │   │   └── example.go
│   │   ├── 03_fixed/
│   │   │   └── example.go
│   │   ├── 04_behaviour.go
│   │   └── 05_behaviour_fixed.go
│   └── 04_interface_segregation_principle/
│       ├── 01_fat_interface.go
│       ├── 02_thin_interface.go
│       ├── 03_repeated_inputs.go
│       ├── 04_repeated_inputs.go
│       ├── 05_repeated_inputs.go
│       └── 06_implicit_interfaces.go
├── ch03/
│   ├── 01_optimizing_for_humans/
│   │   ├── 01_not_so_simple.go
│   │   ├── 02_start_simple.go
│   │   ├── 03_too_abstract.go
│   │   ├── 04_common_concept.go
│   │   ├── 05_boolean_param.go
│   │   ├── 06_hidden_boolean.go
│   │   ├── 07_wide_formatter.go
│   │   ├── 08_thin_formatters.go
│   │   └── 09_extra_config.go
│   ├── 02_unit_tests/
│   │   ├── 01_loader.go
│   │   ├── 02_language_feature.go
│   │   ├── 03_simple_test.go
│   │   ├── 04_test_from_api.go
│   │   ├── 05_repeated_code.go
│   │   ├── 06_tdt.go
│   │   ├── 07_person_loader.go
│   │   ├── 08_stub.go
│   │   ├── 09_stub_tdt.go
│   │   └── 10_mocks.go
│   ├── 03_test_induced_damage/
│   │   ├── 01_io_closer.go
│   │   └── 02_json.go
│   ├── 04_visualizing_dependencies/
│   │   └── depgraph.sh
│   └── fake.go
├── ch04/
│   ├── 01_welcome/
│   │   ├── 01_bad_names.go
│   │   ├── 02_improved_names.go
│   │   ├── 03_long_method.go
│   │   ├── 04_long_method_test.go
│   │   └── 05_short_methods.go
│   ├── 03_known_issues/
│   │   ├── 01_data_and_rest/
│   │   │   └── get_example.go
│   │   └── 02_config_coupling/
│   │       ├── config.go
│   │       └── currency/
│   │           └── currency.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── common_test.go
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch05/
│   ├── 02_advantages/
│   │   ├── 01_function.go
│   │   ├── 02_monkey_patched.go
│   │   ├── 03_injected_lambda.go
│   │   ├── 04_as_object.go
│   │   ├── 05_math_rand.go
│   │   └── 06_math_rand_test.go
│   ├── 03_applying/
│   │   ├── 01_simple_sqlmock_test.go
│   │   └── 02_load.go
│   ├── 04_disadvantages/
│   │   ├── 01_verbose.go
│   │   ├── 02_verbose_test.go
│   │   └── 03_refactored_test.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── common_test.go
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch06/
│   ├── 01_constructor_injection/
│   │   ├── 01_welcome_email.go
│   │   ├── 01_welcome_email_test.go
│   │   ├── 02_mailer_interface.go
│   │   ├── 03_sender_interface.go
│   │   └── 05_duck_typing.go
│   ├── 02_advantages/
│   │   ├── 01_easy_to_implement.go
│   │   ├── 01_easy_to_implement_example_test.go
│   │   ├── 02_easy_to_implement.go
│   │   ├── 02_easy_to_implement_example_test.go
│   │   ├── 03_predictable.go
│   │   ├── 04_predictable.go
│   │   ├── 05_encapsulation.go
│   │   └── 06_encapsulation.go
│   ├── 03_applying/
│   │   ├── 01/
│   │   │   ├── 01_register_handler_before.go
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   └── register/
│   │   │       └── register.go
│   │   ├── 02/
│   │   │   ├── 01_register_handler.go
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   └── register/
│   │   │       └── register.go
│   │   ├── 03/
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   ├── mock_register_model_test.go
│   │   │   └── register_test.go
│   │   ├── 04/
│   │   │   ├── data/
│   │   │   │   └── person.go
│   │   │   ├── mock_register_model_test.go
│   │   │   ├── register.go
│   │   │   └── register_test.go
│   │   └── 05/
│   │       ├── data/
│   │       │   └── person.go
│   │       ├── fakes.go
│   │       ├── get/
│   │       │   └── getter.go
│   │       ├── list/
│   │       │   └── lister.go
│   │       ├── register/
│   │       │   └── registerer.go
│   │       └── server.go
│   ├── 04_disadvantages/
│   │   ├── 01_lots_of_changes.go
│   │   ├── 02_overuse.go
│   │   ├── 03_non_obvious.go
│   │   ├── 04_non_obvious_example_test.go
│   │   └── 05_constructors.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   ├── fake.go
│   └── pcov-html
├── ch07/
│   ├── 01_method_injection/
│   │   ├── 01_fprint.go
│   │   ├── 02_http_request.go
│   │   ├── 03_fprint.go
│   │   ├── 04_http_request.go
│   │   ├── 05_timestamp_writer_v1.go
│   │   ├── 06_timestamp_writer_v2.go
│   │   └── 07_timestamp_writer_v3.go
│   ├── 02_advantages/
│   │   ├── 01_handler_v1.go
│   │   ├── 02_handler_v2.go
│   │   ├── 03_handler_v3.go
│   │   ├── 04_context_influence.go
│   │   └── 05_person_loader.go
│   ├── 04_disadvantages/
│   │   ├── 01_data_struct.go
│   │   ├── 02_ux_improvement.go
│   │   ├── 03_many_params.go
│   │   └── 04_many_params_v2.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   └── converter.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch08/
│   ├── 01_config_injection/
│   │   ├── 01_long_constructor.go
│   │   ├── 02_by_config_example.go
│   │   └── 03_shared_params.go
│   ├── 02_advantages/
│   │   ├── 01_injected_config/
│   │   │   ├── 01.go
│   │   │   └── 01_test.go
│   │   ├── 02_config_injection/
│   │   │   ├── 02.go
│   │   │   └── 02_test.go
│   │   ├── 03_long_constructor.go
│   │   ├── 04_by_config_example.go
│   │   ├── config/
│   │   │   └── config.go
│   │   ├── logging/
│   │   │   └── logger.go
│   │   └── stats/
│   │       └── stats.go
│   ├── 03_applying/
│   │   ├── 01_define_register_config.go
│   │   ├── 02_register_with_config_injection.go
│   │   ├── 03_model_before_data_changes.go
│   │   ├── 04_test_config_link_to_config_package.go
│   │   ├── 05_result_payload.json
│   │   └── 06_simple_test_server.go
│   ├── 04_disadvantages/
│   │   ├── 01_leaking_details.go
│   │   ├── 02_hiding_details.go
│   │   ├── 03_unclear_lifecycle.go
│   │   ├── 04_clear_lifecycle.go
│   │   └── 05_layers.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── data.go
│   │   │   │   │   └── data_test.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   └── go_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   └── list_test.go
│   │   │   │   └── register/
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch09/
│   ├── 01_jit_injection/
│   │   ├── 01_injecting_db.go
│   │   ├── 01_injecting_db_test.go
│   │   ├── 02_injecting_business_logic.go
│   │   ├── 03_injecting_db_jit.go
│   │   ├── 03_injecting_db_jit_test.go
│   │   └── 04_noop_debugger.go
│   ├── 02_advantages/
│   │   ├── 01_long_constructor.go
│   │   ├── 02_short_constructor.go
│   │   ├── 03_optional_dep_without_jitdi.go
│   │   ├── 04_optional_dep_with_jitdi.go
│   │   ├── 05_loader.go
│   │   ├── 06_global_variable/
│   │   │   └── 06_global_variable.go
│   │   ├── 07_global_variable_jit/
│   │   │   ├── 07_global_variable_jit.go
│   │   │   └── 07_global_variable_jit_test.go
│   │   ├── 08_car_v1.go
│   │   └── 09_car_v2.go
│   ├── 03_applying/
│   │   ├── 01_commands.sh
│   │   ├── 02_coverage.txt
│   │   └── 03_initial_dao.go
│   ├── 04_disadvantages/
│   │   ├── 01_uncertain_init_state.go
│   │   ├── 02_certain_init_state.go
│   │   ├── 03_cpool_slow_constructor.go
│   │   └── 04_get_pool_with_once.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── dao.go
│   │   │   │   │   ├── data.go
│   │   │   │   │   ├── data_test.go
│   │   │   │   │   └── tracker.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   ├── go_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   ├── list_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   └── register/
│   │   │   │       ├── mock_my_saver_test.go
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   └── main.go
│   └── fake.go
├── ch10/
│   ├── 01_intro_to_wire/
│   │   ├── 01_simple/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 02_params/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 03_error/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   └── 04_without_pset/
│   │       ├── main.go
│   │       ├── wire.go
│   │       └── wire_gen.go
│   ├── 02_advantages/
│   │   ├── 01_dig/
│   │   │   └── main.go
│   │   └── 02_instantiation_order/
│   │       ├── handler.go
│   │       ├── injectors.go
│   │       ├── main.go
│   │       ├── model.go
│   │       ├── providers.go
│   │       └── wire_gen.go
│   ├── 03_applying/
│   │   ├── 01_before_config/
│   │   │   └── main.go
│   │   ├── 02_after_config/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 03_after_exchange/
│   │   │   ├── main.go
│   │   │   └── wire.go
│   │   ├── 04_after_model/
│   │   │   ├── main.go
│   │   │   └── wire.go
│   │   ├── 05_after_rest/
│   │   │   ├── main.go
│   │   │   ├── wire.go
│   │   │   └── wire_gen.go
│   │   ├── 06_build_tag.go
│   │   ├── 06_build_tag_inverse.go
│   │   └── 06_main.go
│   ├── 04_disadvantages/
│   │   └── 01_complexity/
│   │       └── main.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── dao.go
│   │   │   │   │   ├── data.go
│   │   │   │   │   ├── data_test.go
│   │   │   │   │   └── tracker.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   ├── go_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   ├── list_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   └── register/
│   │   │   │       ├── mock_my_saver_test.go
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   ├── main.go
│   │   ├── main_test.go
│   │   ├── wire.go
│   │   └── wire_gen.go
│   └── fake.go
├── ch11/
│   ├── 01_di_induced_damage/
│   │   ├── 01_long_param/
│   │   │   └── 01_long_param.go
│   │   ├── 02_long_param/
│   │   │   └── 01_long_param.go
│   │   ├── 03_long_param/
│   │   │   └── 01_long_param.go
│   │   ├── 04_long_param/
│   │   │   ├── 01_long_param.go
│   │   │   └── 01_long_param_test.go
│   │   ├── 05_inject_sql/
│   │   │   └── 01_interface.go
│   │   ├── 06_inject_sql/
│   │   │   ├── 01_interface.go
│   │   │   ├── 02_implementation.go
│   │   │   ├── 02_implementation_test.go
│   │   │   ├── dao.go
│   │   │   ├── data.go
│   │   │   └── data_test.go
│   │   ├── 07_needless_indirection/
│   │   │   └── example_test.go
│   │   ├── 08_needless_indirection/
│   │   │   ├── 01_mux.go
│   │   │   ├── 01_mux_test.go
│   │   │   └── mock_my_mux_test.go
│   │   ├── 09_needless_indirection/
│   │   │   ├── 01_mux.go
│   │   │   └── 01_mux_test.go
│   │   ├── 10_needless_indirection/
│   │   │   ├── 01_mux_e2e.go
│   │   │   └── 01_mux_e2e_test.go
│   │   └── 11_service_locator/
│   │       ├── 01_service_locator.go
│   │       └── 02_usage.go
│   ├── 02_premature_future/
│   │   └── get.go
│   ├── 03_mocking_http_requests/
│   │   ├── converter.go
│   │   ├── converter_test.go
│   │   └── mock_requester_test.go
│   └── acme/
│       ├── internal/
│       │   ├── config/
│       │   │   ├── config.go
│       │   │   └── config_test.go
│       │   ├── logging/
│       │   │   └── logging.go
│       │   ├── modules/
│       │   │   ├── data/
│       │   │   │   ├── dao.go
│       │   │   │   ├── data.go
│       │   │   │   ├── data_test.go
│       │   │   │   └── tracker.go
│       │   │   ├── exchange/
│       │   │   │   ├── converter.go
│       │   │   │   ├── converter_ext_bounday_test.go
│       │   │   │   └── converter_int_bounday_test.go
│       │   │   ├── get/
│       │   │   │   ├── get.go
│       │   │   │   ├── go_test.go
│       │   │   │   └── mock_my_loader_test.go
│       │   │   ├── list/
│       │   │   │   ├── list.go
│       │   │   │   ├── list_test.go
│       │   │   │   └── mock_my_loader_test.go
│       │   │   └── register/
│       │   │       ├── mock_exchanger_test.go
│       │   │       ├── mock_my_saver_test.go
│       │   │       ├── register.go
│       │   │       └── register_test.go
│       │   └── rest/
│       │       ├── get.go
│       │       ├── get_test.go
│       │       ├── list.go
│       │       ├── list_test.go
│       │       ├── mock_get_model_test.go
│       │       ├── mock_list_model_test.go
│       │       ├── mock_register_model_test.go
│       │       ├── not_found.go
│       │       ├── not_found_test.go
│       │       ├── register.go
│       │       ├── register_test.go
│       │       └── server.go
│       ├── main.go
│       ├── main_test.go
│       ├── wire.go
│       └── wire_gen.go
├── ch12/
│   ├── 01_improvements/
│   │   └── 01_test_logging_test.go
│   ├── 03_testing/
│   │   ├── 01_mock_get_model.go
│   │   ├── 02_coverage_ch04.txt
│   │   ├── 03_coverage_ch11.txt
│   │   ├── 04_coverage_config.htm
│   │   ├── 04_coverage_data.htm
│   │   ├── 04_coverage_exchange.htm
│   │   ├── 04_coverage_get.htm
│   │   ├── 04_coverage_list.htm
│   │   ├── 04_coverage_main.htm
│   │   ├── 04_coverage_register.htm
│   │   └── 04_coverage_rest.htm
│   ├── 04_new_service/
│   │   └── 01_data_with_cache/
│   │       ├── dao.go
│   │       ├── data.go
│   │       └── internal/
│   │           ├── cache/
│   │           │   └── cache.go
│   │           └── logging/
│   │               └── logging.go
│   ├── acme/
│   │   ├── internal/
│   │   │   ├── config/
│   │   │   │   ├── config.go
│   │   │   │   └── config_test.go
│   │   │   ├── logging/
│   │   │   │   └── logging.go
│   │   │   ├── modules/
│   │   │   │   ├── data/
│   │   │   │   │   ├── dao.go
│   │   │   │   │   ├── data.go
│   │   │   │   │   ├── data_test.go
│   │   │   │   │   └── tracker.go
│   │   │   │   ├── exchange/
│   │   │   │   │   ├── converter.go
│   │   │   │   │   ├── converter_ext_bounday_test.go
│   │   │   │   │   └── converter_int_bounday_test.go
│   │   │   │   ├── get/
│   │   │   │   │   ├── get.go
│   │   │   │   │   ├── go_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   ├── list/
│   │   │   │   │   ├── list.go
│   │   │   │   │   ├── list_test.go
│   │   │   │   │   └── mock_my_loader_test.go
│   │   │   │   └── register/
│   │   │   │       ├── mock_exchanger_test.go
│   │   │   │       ├── mock_my_saver_test.go
│   │   │   │       ├── register.go
│   │   │   │       └── register_test.go
│   │   │   └── rest/
│   │   │       ├── get.go
│   │   │       ├── get_test.go
│   │   │       ├── list.go
│   │   │       ├── list_test.go
│   │   │       ├── mock_get_model_test.go
│   │   │       ├── mock_list_model_test.go
│   │   │       ├── mock_register_model_test.go
│   │   │       ├── not_found.go
│   │   │       ├── not_found_test.go
│   │   │       ├── register.go
│   │   │       ├── register_test.go
│   │   │       └── server.go
│   │   ├── main.go
│   │   ├── main_test.go
│   │   ├── wire.go
│   │   └── wire_gen.go
│   └── fake.go
├── default-config.json
├── fake.go
├── resources/
│   └── create.sql
└── vendor/
    ├── github.com/
    │   ├── DATA-DOG/
    │   │   └── go-sqlmock/
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── argument.go
    │   │       ├── driver.go
    │   │       ├── expectations.go
    │   │       ├── expectations_before_go18.go
    │   │       ├── expectations_go18.go
    │   │       ├── result.go
    │   │       ├── rows.go
    │   │       ├── rows_go18.go
    │   │       ├── sqlmock.go
    │   │       ├── sqlmock_go18.go
    │   │       ├── statement.go
    │   │       └── util.go
    │   ├── davecgh/
    │   │   └── go-spew/
    │   │       ├── LICENSE
    │   │       └── spew/
    │   │           ├── bypass.go
    │   │           ├── bypasssafe.go
    │   │           ├── common.go
    │   │           ├── config.go
    │   │           ├── doc.go
    │   │           ├── dump.go
    │   │           ├── format.go
    │   │           └── spew.go
    │   ├── go-sql-driver/
    │   │   └── mysql/
    │   │       ├── AUTHORS
    │   │       ├── CHANGELOG.md
    │   │       ├── CONTRIBUTING.md
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── appengine.go
    │   │       ├── buffer.go
    │   │       ├── collations.go
    │   │       ├── connection.go
    │   │       ├── connection_go18.go
    │   │       ├── const.go
    │   │       ├── driver.go
    │   │       ├── dsn.go
    │   │       ├── errors.go
    │   │       ├── fields.go
    │   │       ├── infile.go
    │   │       ├── packets.go
    │   │       ├── result.go
    │   │       ├── rows.go
    │   │       ├── statement.go
    │   │       ├── transaction.go
    │   │       ├── utils.go
    │   │       ├── utils_go17.go
    │   │       └── utils_go18.go
    │   ├── google/
    │   │   └── wire/
    │   │       ├── AUTHORS
    │   │       ├── CODE_OF_CONDUCT.md
    │   │       ├── CONTRIBUTING.md
    │   │       ├── CONTRIBUTORS
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── go.mod
    │   │       ├── go.sum
    │   │       └── wire.go
    │   ├── gorilla/
    │   │   ├── context/
    │   │   │   ├── LICENSE
    │   │   │   ├── README.md
    │   │   │   ├── context.go
    │   │   │   └── doc.go
    │   │   └── mux/
    │   │       ├── ISSUE_TEMPLATE.md
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       ├── context_gorilla.go
    │   │       ├── context_native.go
    │   │       ├── doc.go
    │   │       ├── middleware.go
    │   │       ├── mux.go
    │   │       ├── regexp.go
    │   │       ├── route.go
    │   │       └── test_helpers.go
    │   ├── pmezard/
    │   │   └── go-difflib/
    │   │       ├── LICENSE
    │   │       └── difflib/
    │   │           └── difflib.go
    │   └── stretchr/
    │       ├── objx/
    │       │   ├── LICENSE.md
    │       │   ├── README.md
    │       │   ├── accessors.go
    │       │   ├── constants.go
    │       │   ├── conversions.go
    │       │   ├── doc.go
    │       │   ├── map.go
    │       │   ├── mutations.go
    │       │   ├── security.go
    │       │   ├── tests.go
    │       │   ├── type_specific_codegen.go
    │       │   └── value.go
    │       └── testify/
    │           ├── LICENSE
    │           ├── assert/
    │           │   ├── assertion_format.go
    │           │   ├── assertion_format.go.tmpl
    │           │   ├── assertion_forward.go
    │           │   ├── assertion_forward.go.tmpl
    │           │   ├── assertions.go
    │           │   ├── doc.go
    │           │   ├── errors.go
    │           │   ├── forward_assertions.go
    │           │   └── http_assertions.go
    │           ├── mock/
    │           │   ├── doc.go
    │           │   └── mock.go
    │           └── require/
    │               ├── doc.go
    │               ├── forward_requirements.go
    │               ├── require.go
    │               ├── require.go.tmpl
    │               ├── require_forward.go
    │               ├── require_forward.go.tmpl
    │               └── requirements.go
    ├── go.uber.org/
    │   └── dig/
    │       ├── CHANGELOG.md
    │       ├── LICENSE
    │       ├── Makefile
    │       ├── README.md
    │       ├── check_license.sh
    │       ├── cycle.go
    │       ├── dig.go
    │       ├── doc.go
    │       ├── error.go
    │       ├── glide.yaml
    │       ├── internal/
    │       │   ├── digreflect/
    │       │   │   └── func.go
    │       │   └── dot/
    │       │       └── graph.go
    │       ├── param.go
    │       ├── result.go
    │       ├── stringer.go
    │       ├── types.go
    │       └── version.go
    ├── google.golang.org/
    │   └── appengine/
    │       ├── LICENSE
    │       └── cloudsql/
    │           ├── cloudsql.go
    │           ├── cloudsql_classic.go
    │           └── cloudsql_vm.go
    └── vendor.json
Download .txt
Showing preview only (319K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3579 symbols across 591 files)

FILE: ch01/01_defining_depenency_injection/01_interface.go
  type Saver (line 9) | type Saver interface
  function SavePerson (line 14) | func SavePerson(person *Person, saver Saver) error {
  type Person (line 33) | type Person struct
    method validate (line 39) | func (p *Person) validate() error {
    method encode (line 52) | func (p *Person) encode() ([]byte, error) {

FILE: ch01/01_defining_depenency_injection/02_function_literal.go
  function LoadPerson (line 10) | func LoadPerson(ID int, decodePerson func(data []byte) *Person) (*Person...
  function loadPerson (line 27) | func loadPerson(ID int) ([]byte, error) {

FILE: ch01/01_defining_depenency_injection/03_test_without_nfs_test.go
  function TestSavePerson_happyPath (line 10) | func TestSavePerson_happyPath(t *testing.T) {
  type mockSaver (line 30) | type mockSaver struct
    method Save (line 35) | func (m *mockSaver) Save(data []byte) error {

FILE: ch01/01_defining_depenency_injection/04_fail_test_without_nfs_test.go
  function TestSavePerson_nfsAlwaysFails (line 11) | func TestSavePerson_nfsAlwaysFails(t *testing.T) {

FILE: ch01/02_code_smells/01_code_bloat/01_switch_type.go
  function AppendValue (line 7) | func AppendValue(buffer []byte, in interface{}) []byte {

FILE: ch01/02_code_smells/02_resistance_to_change/01_shotgun_surgey.go
  type Renderer (line 9) | type Renderer struct
    method render (line 11) | func (r Renderer) render(name, phone string, output io.Writer) {
  type Validator (line 16) | type Validator struct
    method validate (line 18) | func (v Validator) validate(name, phone string) error {
  type Saver (line 24) | type Saver struct
    method Save (line 26) | func (s *Saver) Save(db *sql.DB, name, phone string) {

FILE: ch01/02_code_smells/03_wasted_effort/01_excessive_comments.go
  function outputOrderedPeopleA (line 4) | func outputOrderedPeopleA(in []*Person) {
  function outputOrderedPeopleB (line 17) | func outputOrderedPeopleB(in []*Person) {
  function outputPeople (line 22) | func outputPeople(in []*Person) {
  function sortPeople (line 27) | func sortPeople(in []*Person) {
  type Person (line 32) | type Person struct

FILE: ch01/02_code_smells/03_wasted_effort/02_complicated_go.go
  function d (line 9) | func d(r, v float64, i *image.RGBA, c color.Color) {

FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config/config.go
  type Config (line 12) | type Config struct
  function Load (line 22) | func Load(filename string) (*Config, error) {

FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment/currency.go
  type Currency (line 12) | type Currency
  type Processor (line 15) | type Processor struct
    method Pay (line 20) | func (p *Processor) Pay(amount float64) error {

FILE: ch01/02_code_smells/04_tight_coupling/02_object_orgy.go
  type PageLoader (line 9) | type PageLoader struct
    method LoadPage (line 12) | func (o *PageLoader) LoadPage(url string) ([]byte, error) {
  type Fetcher (line 44) | type Fetcher struct
  function newFetcher (line 49) | func newFetcher() *Fetcher {
  type Cache (line 53) | type Cache struct
    method Get (line 57) | func (c *Cache) Get(key string) ([]byte, error) {
    method Set (line 62) | func (c *Cache) Set(key string, data []byte) error {

FILE: ch01/02_code_smells/04_tight_coupling/03_feature_envy.go
  type searchRequest (line 8) | type searchRequest struct
    method validate (line 14) | func (request searchRequest) validate() error {
  type searchResults (line 28) | type searchResults struct
  function doSearchWithEnvy (line 32) | func doSearchWithEnvy(request searchRequest) ([]searchResults, error) {
  function doSearchWithoutEnvy (line 47) | func doSearchWithoutEnvy(request searchRequest) ([]searchResults, error) {
  function performSearch (line 56) | func performSearch(request searchRequest) ([]searchResults, error) {

FILE: ch02/01_single_responsibility_principle/01_responsibility_vs_change.go
  type CalculatorV1 (line 9) | type CalculatorV1 struct
    method Calculate (line 15) | func (c *CalculatorV1) Calculate(path string) error {
    method Output (line 21) | func (c *CalculatorV1) Output(writer io.Writer) {

FILE: ch02/01_single_responsibility_principle/02_responsibility_vs_change.go
  type CalculatorV2 (line 9) | type CalculatorV2 struct
    method Calculate (line 15) | func (c *CalculatorV2) Calculate(path string) error {
    method Output (line 21) | func (c CalculatorV2) Output(writer io.Writer) {
    method OutputCSV (line 28) | func (c CalculatorV2) OutputCSV(writer io.Writer) {

FILE: ch02/01_single_responsibility_principle/03_responsibility_vs_change.go
  type CalculatorV3 (line 9) | type CalculatorV3 struct
    method Calculate (line 15) | func (c *CalculatorV3) Calculate(path string) error {
    method getData (line 20) | func (c *CalculatorV3) getData() map[string]float64 {
  type Printer (line 25) | type Printer interface
  type DefaultPrinter (line 29) | type DefaultPrinter struct
    method Output (line 34) | func (d *DefaultPrinter) Output(data map[string]float64) {
  type CSVPrinter (line 40) | type CSVPrinter struct
    method Output (line 45) | func (d *CSVPrinter) Output(data map[string]float64) {

FILE: ch02/01_single_responsibility_principle/04_long_method.go
  function loadUserHandlerLong (line 10) | func loadUserHandlerLong(resp http.ResponseWriter, req *http.Request) {
  type Person (line 41) | type Person struct

FILE: ch02/01_single_responsibility_principle/04_long_method_test.go
  function TestLoadUserHandler (line 14) | func TestLoadUserHandler(t *testing.T) {
  function TestMain (line 32) | func TestMain(m *testing.M) {

FILE: ch02/01_single_responsibility_principle/05_srp_method.go
  function loadUserHandlerSRP (line 9) | func loadUserHandlerSRP(resp http.ResponseWriter, req *http.Request) {
  function extractIDFromRequest (line 25) | func extractIDFromRequest(req *http.Request) (int64, error) {
  function loadPersonByID (line 33) | func loadPersonByID(userID int64) (*Person, error) {
  function outputPerson (line 44) | func outputPerson(resp http.ResponseWriter, person *Person) {

FILE: ch02/02_open_closed_principle/01_open_closed_failure.go
  function BuildOutputOCPFail (line 8) | func BuildOutputOCPFail(response http.ResponseWriter, format string, per...
  function outputCSV (line 29) | func outputCSV(writer io.Writer, person Person) error {
  function outputJSON (line 35) | func outputJSON(writer io.Writer, person Person) error {
  type Person (line 41) | type Person struct

FILE: ch02/02_open_closed_principle/02_open_closed_success.go
  function BuildOutputOCPSuccess (line 8) | func BuildOutputOCPSuccess(response http.ResponseWriter, formatter Perso...
  type PersonFormatter (line 19) | type PersonFormatter interface
  type CSVPersonFormatter (line 24) | type CSVPersonFormatter struct
    method Format (line 27) | func (c *CSVPersonFormatter) Format(writer io.Writer, person Person) e...
  type JSONPersonFormatter (line 33) | type JSONPersonFormatter struct
    method Format (line 36) | func (j *JSONPersonFormatter) Format(writer io.Writer, person Person) ...

FILE: ch02/02_open_closed_principle/03_shotgun_surgery.go
  function GetUserHandlerV1 (line 8) | func GetUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
  function DeleteUserHandlerV1 (line 25) | func DeleteUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
  function loadUser (line 41) | func loadUser(userID int64) interface{} {
  function deleteUser (line 46) | func deleteUser(userID int64) {
  function outputUser (line 50) | func outputUser(resp http.ResponseWriter, user interface{}) {

FILE: ch02/02_open_closed_principle/04_after_shotgun_surgery.go
  function GetUserHandlerV2 (line 10) | func GetUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
  function DeleteUserHandlerV2 (line 27) | func DeleteUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
  function extractUserID (line 43) | func extractUserID(values url.Values) (int64, error) {

FILE: ch02/02_open_closed_principle/05_composition.go
  type rowConverter (line 7) | type rowConverter struct
    method populate (line 11) | func (d *rowConverter) populate(in *Person, scan func(dest ...interfac...
  type LoadPerson (line 15) | type LoadPerson struct
    method ByID (line 20) | func (loader *LoadPerson) ByID(id int) (Person, error) {
    method loadFromDB (line 30) | func (loader *LoadPerson) loadFromDB(id int) *sql.Row {
    method All (line 40) | func (loader *LoadPerson) All() ([]Person, error) {
    method loadAllFromDB (line 58) | func (loader *LoadPerson) loadAllFromDB() *sql.Rows {
  type LoadAll (line 35) | type LoadAll struct

FILE: ch02/02_open_closed_principle/06_handler_struct.go
  type healthCheckLong (line 8) | type healthCheckLong struct
    method ServeHTTP (line 11) | func (h *healthCheckLong) ServeHTTP(resp http.ResponseWriter, _ *http....
  function healthCheckLongUsage (line 15) | func healthCheckLongUsage() {

FILE: ch02/02_open_closed_principle/07_handler_func.go
  function healthCheckShort (line 8) | func healthCheckShort(resp http.ResponseWriter, _ *http.Request) {
  function healthCheckShortUsage (line 12) | func healthCheckShortUsage() {

FILE: ch02/03_liskov_substitution_principle/01_violation/example.go
  function Go (line 3) | func Go(vehicle actions) {
  type actions (line 13) | type actions interface
  type Vehicle (line 18) | type Vehicle struct
    method drive (line 21) | func (v Vehicle) drive() {
    method startEngine (line 25) | func (v Vehicle) startEngine() {
    method stopEngine (line 29) | func (v Vehicle) stopEngine() {
  type Car (line 33) | type Car struct
  type Sled (line 37) | type Sled struct
    method startEngine (line 41) | func (s Sled) startEngine() {
    method stopEngine (line 45) | func (s Sled) stopEngine() {
    method pushStart (line 49) | func (s Sled) pushStart() {

FILE: ch02/03_liskov_substitution_principle/02_fixed/example.go
  function Go (line 3) | func Go(vehicle actions) {
  type actions (line 15) | type actions interface
  type poweredActions (line 19) | type poweredActions interface
  type unpoweredActions (line 25) | type unpoweredActions interface
  type Vehicle (line 30) | type Vehicle struct
    method drive (line 33) | func (v Vehicle) drive() {
  type PoweredVehicle (line 37) | type PoweredVehicle struct
    method startEngine (line 41) | func (v PoweredVehicle) startEngine() {
  type Car (line 45) | type Car struct
  type Sled (line 49) | type Sled struct
    method pushStart (line 53) | func (s Sled) pushStart() {

FILE: ch02/03_liskov_substitution_principle/03_fixed/example.go
  function Go (line 3) | func Go(vehicle actions) {
  type actions (line 8) | type actions interface
  type Car (line 13) | type Car struct
    method start (line 17) | func (c Car) start() {
    method drive (line 21) | func (c Car) drive() {
  type poweredVehicle (line 25) | type poweredVehicle struct
    method startEngine (line 28) | func (p poweredVehicle) startEngine() {
  type Sled (line 32) | type Sled struct
    method start (line 35) | func (s Sled) start() {
    method drive (line 39) | func (s Sled) drive() {

FILE: ch02/03_liskov_substitution_principle/04_behaviour.go
  type Collection (line 3) | type Collection interface
  type CollectionImpl (line 8) | type CollectionImpl struct
    method Add (line 12) | func (c *CollectionImpl) Add(item interface{}) {
    method Get (line 16) | func (c *CollectionImpl) Get(index int) interface{} {
  type ReadOnlyCollection (line 20) | type ReadOnlyCollection struct
    method Add (line 24) | func (ro *ReadOnlyCollection) Add(item interface{}) {

FILE: ch02/03_liskov_substitution_principle/05_behaviour_fixed.go
  type ImmutableCollection (line 3) | type ImmutableCollection interface
  type MutableCollection (line 7) | type MutableCollection interface
  type ReadOnlyCollectionV2 (line 12) | type ReadOnlyCollectionV2 struct
    method Get (line 16) | func (ro *ReadOnlyCollectionV2) Get(index int) interface{} {
  type CollectionImplV2 (line 20) | type CollectionImplV2 struct
    method Add (line 24) | func (c *CollectionImplV2) Add(item interface{}) {

FILE: ch02/04_interface_segregation_principle/01_fat_interface.go
  type Item (line 7) | type Item struct
  type FatDbInterface (line 12) | type FatDbInterface interface
  type Cache (line 35) | type Cache struct
    method Get (line 39) | func (c *Cache) Get(key string) interface{} {
    method Set (line 49) | func (c *Cache) Set(key string, value interface{}) {

FILE: ch02/04_interface_segregation_principle/02_thin_interface.go
  type myDB (line 3) | type myDB interface
  type CacheV2 (line 8) | type CacheV2 struct
    method Get (line 12) | func (c *CacheV2) Get(key string) interface{} {
    method Set (line 22) | func (c *CacheV2) Set(key string, value interface{}) {

FILE: ch02/04_interface_segregation_principle/03_repeated_inputs.go
  function Encrypt (line 8) | func Encrypt(ctx context.Context, data []byte) ([]byte, error) {
  function performEncryption (line 41) | func performEncryption(key []byte, data []byte) []byte {

FILE: ch02/04_interface_segregation_principle/04_repeated_inputs.go
  type Value (line 7) | type Value interface
  type Monitor (line 11) | type Monitor interface
  function EncryptV2 (line 15) | func EncryptV2(keyValue Value, monitor Monitor, data []byte) ([]byte, er...

FILE: ch02/04_interface_segregation_principle/05_repeated_inputs.go
  function UseEncryptV2 (line 7) | func UseEncryptV2() {

FILE: ch02/04_interface_segregation_principle/06_implicit_interfaces.go
  type Talker (line 7) | type Talker interface
  type Dog (line 11) | type Dog struct
    method SayHello (line 14) | func (d Dog) SayHello() string {
  function Speak (line 18) | func Speak() {

FILE: ch03/01_optimizing_for_humans/01_not_so_simple.go
  function NotSoSimple (line 9) | func NotSoSimple(ID int64, name string, age int, registered bool) string {

FILE: ch03/01_optimizing_for_humans/02_start_simple.go
  function Simpler (line 8) | func Simpler(ID int64, name string, age int, registered bool) string {

FILE: ch03/01_optimizing_for_humans/03_too_abstract.go
  type myGetter (line 8) | type myGetter interface
  function TooAbstract (line 12) | func TooAbstract(getter myGetter, url string) ([]byte, error) {

FILE: ch03/01_optimizing_for_humans/04_common_concept.go
  function CommonConcept (line 8) | func CommonConcept(url string) ([]byte, error) {

FILE: ch03/01_optimizing_for_humans/05_boolean_param.go
  type Pet (line 7) | type Pet struct
  function NewPet (line 13) | func NewPet(name string, isDog bool) Pet {
  function CreatePetsV1 (line 21) | func CreatePetsV1() {

FILE: ch03/01_optimizing_for_humans/06_hidden_boolean.go
  constant isDog (line 4) | isDog = true
  constant isCat (line 5) | isCat = false
  function NewDog (line 8) | func NewDog(name string) Pet {
  function NewCat (line 12) | func NewCat(name string) Pet {
  function CreatePetsV2 (line 16) | func CreatePetsV2() {

FILE: ch03/01_optimizing_for_humans/07_wide_formatter.go
  type WideFormatter (line 3) | type WideFormatter interface

FILE: ch03/01_optimizing_for_humans/08_thin_formatters.go
  type ThinFormatter (line 3) | type ThinFormatter interface
  type CSVFormatter (line 7) | type CSVFormatter struct
    method Format (line 9) | func (f CSVFormatter) Format(pets []Pet) ([]byte, error) {
  type GOBFormatter (line 14) | type GOBFormatter struct
    method Format (line 16) | func (f GOBFormatter) Format(pets []Pet) ([]byte, error) {
  type JSONFormatter (line 21) | type JSONFormatter struct
    method Format (line 23) | func (f JSONFormatter) Format(pets []Pet) ([]byte, error) {

FILE: ch03/01_optimizing_for_humans/09_extra_config.go
  function PetFetcher (line 6) | func PetFetcher(search string, limit int, offset int, sortBy string, sor...
  function PetFetcherTypicalUsage (line 10) | func PetFetcherTypicalUsage() {

FILE: ch03/02_unit_tests/01_loader.go
  type Loader (line 13) | type Loader interface
  function TestLoadAndPrint_happyPath (line 17) | func TestLoadAndPrint_happyPath(t *testing.T) {
  function TestLoadAndPrint_notFound (line 23) | func TestLoadAndPrint_notFound(t *testing.T) {
  function TestLoadAndPrint_error (line 29) | func TestLoadAndPrint_error(t *testing.T) {
  function LoadAndPrint (line 35) | func LoadAndPrint(loader Loader, ID int, dest io.Writer) {
  type happyPathLoader (line 51) | type happyPathLoader struct
    method Load (line 54) | func (l *happyPathLoader) Load(ID int) (*Pet, error) {
  type missingLoader (line 59) | type missingLoader struct
    method Load (line 62) | func (l *missingLoader) Load(ID int) (*Pet, error) {
  type errorLoader (line 67) | type errorLoader struct
    method Load (line 70) | func (l *errorLoader) Load(ID int) (*Pet, error) {

FILE: ch03/02_unit_tests/02_language_feature.go
  type Pet (line 9) | type Pet struct
  function NewPet (line 13) | func NewPet(name string) *Pet {
  function TestLanguageFeatures (line 19) | func TestLanguageFeatures(t *testing.T) {

FILE: ch03/02_unit_tests/03_simple_test.go
  function concat (line 9) | func concat(a, b string) string {
  function TestTooSimple (line 13) | func TestTooSimple(t *testing.T) {

FILE: ch03/02_unit_tests/04_test_from_api.go
  type PetSaver (line 7) | type PetSaver struct
    method Save (line 10) | func (p PetSaver) Save(pet Pet) (int, error) {
    method validate (line 25) | func (p PetSaver) validate(pet Pet) error {
    method save (line 30) | func (p PetSaver) save(pet Pet) (sql.Result, error) {
    method extractID (line 35) | func (p PetSaver) extractID(result sql.Result) (int, error) {

FILE: ch03/02_unit_tests/05_repeated_code.go
  function Round (line 10) | func Round(in float64) int {
  function TestRound_down (line 14) | func TestRound_down(t *testing.T) {
  function TestRound_up (line 22) | func TestRound_up(t *testing.T) {
  function TestRound_noChange (line 30) | func TestRound_noChange(t *testing.T) {

FILE: ch03/02_unit_tests/06_tdt.go
  function TestRound (line 9) | func TestRound(t *testing.T) {

FILE: ch03/02_unit_tests/07_person_loader.go
  type Person (line 9) | type Person struct
  type PersonLoader (line 14) | type PersonLoader interface
  function LoadPersonName (line 18) | func LoadPersonName(loader PersonLoader, ID int) (string, error) {

FILE: ch03/02_unit_tests/08_stub.go
  type PersonLoaderStub (line 4) | type PersonLoaderStub struct
    method Load (line 9) | func (p *PersonLoaderStub) Load(ID int) (*Person, error) {

FILE: ch03/02_unit_tests/09_stub_tdt.go
  function TestLoadPersonNameStubs (line 10) | func TestLoadPersonNameStubs(t *testing.T) {

FILE: ch03/02_unit_tests/10_mocks.go
  function TestLoadPersonName (line 11) | func TestLoadPersonName(t *testing.T) {
  type PersonLoaderMock (line 66) | type PersonLoaderMock struct
    method Load (line 70) | func (p *PersonLoaderMock) Load(ID int) (*Person, error) {

FILE: ch03/03_test_induced_damage/01_io_closer.go
  function WriteAndClose (line 7) | func WriteAndClose(destination io.WriteCloser, contents string) error {

FILE: ch03/03_test_induced_damage/02_json.go
  function PrintAsJSON (line 8) | func PrintAsJSON(destination io.Writer, plant Plant) error {
  type Plant (line 18) | type Plant struct

FILE: ch03/fake.go
  function init (line 3) | func init() {

FILE: ch04/01_welcome/01_bad_names.go
  type HouseV1 (line 3) | type HouseV1 struct

FILE: ch04/01_welcome/02_improved_names.go
  type HouseV2 (line 3) | type HouseV2 struct

FILE: ch04/01_welcome/03_long_method.go
  function longMethod (line 10) | func longMethod(resp http.ResponseWriter, req *http.Request) {
  type Person (line 41) | type Person struct

FILE: ch04/01_welcome/04_long_method_test.go
  function TestLongMethod_happyPath (line 15) | func TestLongMethod_happyPath(t *testing.T) {

FILE: ch04/01_welcome/05_short_methods.go
  function shortMethods (line 9) | func shortMethods(resp http.ResponseWriter, req *http.Request) {
  function extractUserID (line 25) | func extractUserID(req *http.Request) (int64, error) {
  function loadPerson (line 34) | func loadPerson(userID int64) (*Person, error) {
  function outputPerson (line 45) | func outputPerson(resp http.ResponseWriter, person *Person) {

FILE: ch04/03_known_issues/01_data_and_rest/get_example.go
  method writeJSON (line 13) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) er...

FILE: ch04/03_known_issues/02_config_coupling/config.go
  type Config (line 7) | type Config struct

FILE: ch04/03_known_issues/02_config_coupling/currency/currency.go
  type Currency (line 9) | type Currency
    method UnmarshalJSON (line 12) | func (c *Currency) UnmarshalJSON(in []byte) error {
  constant AUD (line 30) | AUD = Currency("AUD")
  constant CNY (line 31) | CNY = Currency("CNY")
  constant EUR (line 32) | EUR = Currency("EUR")
  constant USD (line 33) | USD = Currency("USD")

FILE: ch04/acme/internal/config/config.go
  constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 18) | type Config struct
  function init (line 36) | func init() {
  function load (line 46) | func load(filename string) error {

FILE: ch04/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch04/acme/internal/logging/logging.go
  type LoggerStdOut (line 11) | type LoggerStdOut struct
    method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch04/acme/internal/modules/data/data.go
  constant defaultPersonID (line 15) | defaultPersonID = 0
  function getDB (line 25) | func getDB() (*sql.DB, error) {
  type Person (line 43) | type Person struct
  function Save (line 58) | func Save(in *Person) (int, error) {
  function LoadAll (line 85) | func LoadAll() ([]*Person, error) {
  function Load (line 126) | func Load(ID int) (*Person, error) {
  type scanner (line 152) | type scanner
  function populatePerson (line 155) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch04/acme/internal/modules/data/data_test.go
  function TestData_happyPath (line 10) | func TestData_happyPath(t *testing.T) {

FILE: ch04/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 16) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 19) | defaultPrice = 0.0
  type Converter (line 24) | type Converter struct
    method Do (line 27) | func (c *Converter) Do(basePrice float64, currency string) (float64, e...
    method loadRateFromServer (line 45) | func (c *Converter) loadRateFromServer(currency string) (*http.Respons...
    method extractRate (line 68) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 91) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
  type apiResponseFormat (line 110) | type apiResponseFormat struct

FILE: ch04/acme/internal/modules/get/get.go
  type Getter (line 16) | type Getter struct
    method Do (line 20) | func (g *Getter) Do(ID int) (*data.Person, error) {

FILE: ch04/acme/internal/modules/get/go_test.go
  function TestGetter_Do (line 10) | func TestGetter_Do(t *testing.T) {

FILE: ch04/acme/internal/modules/list/list.go
  type Lister (line 16) | type Lister struct
    method Do (line 20) | func (l *Lister) Do() ([]*data.Person, error) {
    method load (line 36) | func (l *Lister) load() ([]*data.Person, error) {

FILE: ch04/acme/internal/modules/list/list_test.go
  function TestLister_Do (line 10) | func TestLister_Do(t *testing.T) {

FILE: ch04/acme/internal/modules/register/register.go
  constant defaultPersonID (line 14) | defaultPersonID = 0
  type Registerer (line 43) | type Registerer struct
    method Do (line 47) | func (r *Registerer) Do(in *data.Person) (int, error) {
    method validateInput (line 72) | func (r *Registerer) validateInput(in *data.Person) error {
    method getPrice (line 92) | func (r *Registerer) getPrice(currency string) (float64, error) {
    method save (line 104) | func (r *Registerer) save(in *data.Person, price float64) (int, error) {

FILE: ch04/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do (line 11) | func TestRegisterer_Do(t *testing.T) {

FILE: ch04/acme/internal/rest/common_test.go
  function getOpenPort (line 8) | func getOpenPort() (string, error) {
  function startServer (line 20) | func startServer(ctx context.Context) (string, error) {

FILE: ch04/acme/internal/rest/get.go
  constant defaultPersonID (line 19) | defaultPersonID = 0
  type GetHandler (line 26) | type GetHandler struct
    method ServeHTTP (line 30) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 57) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 81) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
  type getResponseFormat (line 95) | type getResponseFormat struct

FILE: ch04/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 14) | func TestGetHandler_ServeHTTP(t *testing.T) {

FILE: ch04/acme/internal/rest/list.go
  type ListHandler (line 14) | type ListHandler struct
    method ServeHTTP (line 18) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 37) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
  type listResponseFormat (line 54) | type listResponseFormat struct
  type listResponseItemFormat (line 58) | type listResponseItemFormat struct

FILE: ch04/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 14) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch04/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch04/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 12) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch04/acme/internal/rest/register.go
  type RegisterHandler (line 15) | type RegisterHandler struct
    method ServeHTTP (line 19) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 42) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 55) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
  type registerRequest (line 67) | type registerRequest struct

FILE: ch04/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 16) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRequest (line 40) | func buildValidRequest() io.Reader {

FILE: ch04/acme/internal/rest/server.go
  function New (line 10) | func New(address string) *Server {
  type Server (line 21) | type Server struct
    method Listen (line 32) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 54) | func (s *Server) buildRouter() http.Handler {

FILE: ch04/acme/main.go
  function main (line 10) | func main() {

FILE: ch04/fake.go
  function init (line 3) | func init() {

FILE: ch05/02_advantages/01_function.go
  function SaveConfig (line 9) | func SaveConfig(filename string, cfg *Config) error {
  type Config (line 26) | type Config struct

FILE: ch05/02_advantages/02_monkey_patched.go
  function SaveConfigPatched (line 10) | func SaveConfigPatched(filename string, cfg *Config) error {
  function SaveConfigPatchedUsage (line 31) | func SaveConfigPatchedUsage() {

FILE: ch05/02_advantages/03_injected_lambda.go
  function SaveConfigInjected (line 11) | func SaveConfigInjected(writer fileWriter, filename string, cfg *Config)...
  type fileWriter (line 30) | type fileWriter
  function SaveConfigInjectedUsage (line 33) | func SaveConfigInjectedUsage() {

FILE: ch05/02_advantages/04_as_object.go
  type ConfigSaver (line 11) | type ConfigSaver struct
    method Save (line 15) | func (c ConfigSaver) Save(filename string, cfg *Config) error {
  function ConfigSaverUsage (line 33) | func ConfigSaverUsage() {

FILE: ch05/02_advantages/05_math_rand.go
  type Rand (line 4) | type Rand struct
    method Int (line 11) | func (r *Rand) Int() int {
  function Int (line 24) | func Int() int { return globalRand.Int() }
  function New (line 33) | func New(src Source) *Rand {
  type lockedSource (line 40) | type lockedSource struct
    method Int63 (line 44) | func (l *lockedSource) Int63() int64 {
  type Source (line 51) | type Source interface

FILE: ch05/02_advantages/06_math_rand_test.go
  function TestInt (line 9) | func TestInt(t *testing.T) {
  type stubSource (line 26) | type stubSource struct
    method Int63 (line 29) | func (s *stubSource) Int63() int64 {

FILE: ch05/03_applying/01_simple_sqlmock_test.go
  function TestSave_happyPath (line 12) | func TestSave_happyPath(t *testing.T) {
  function SavePerson (line 43) | func SavePerson(db *sql.DB, in *Person) (int, error) {

FILE: ch05/03_applying/02_load.go
  constant sqlAllColumns (line 15) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlLoadByID (line 16) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  function TestLoad_happyPath (line 19) | func TestLoad_happyPath(t *testing.T) {
  function convertSQLToRegex (line 57) | func convertSQLToRegex(in string) string {
  function Load (line 64) | func Load(ID int) (*Person, error) {
  type Person (line 79) | type Person struct

FILE: ch05/04_disadvantages/01_verbose.go
  function SaveConfig (line 9) | func SaveConfig(filename string, cfg *Config) error {
  type Config (line 29) | type Config struct

FILE: ch05/04_disadvantages/02_verbose_test.go
  function TestSaveConfig (line 10) | func TestSaveConfig(t *testing.T) {

FILE: ch05/04_disadvantages/03_refactored_test.go
  function TestSaveConfig_refactored (line 10) | func TestSaveConfig_refactored(t *testing.T) {
  function mockWriteFile (line 30) | func mockWriteFile(result error) func(filename string, data []byte, perm...
  function restoreWriteFile (line 37) | func restoreWriteFile(original func(filename string, data []byte, perm o...

FILE: ch05/acme/internal/config/config.go
  constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 18) | type Config struct
  function init (line 36) | func init() {
  function load (line 46) | func load(filename string) error {

FILE: ch05/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch05/acme/internal/logging/logging.go
  type LoggerStdOut (line 11) | type LoggerStdOut struct
    method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch05/acme/internal/modules/data/data.go
  constant defaultPersonID (line 15) | defaultPersonID = 0
  constant sqlAllColumns (line 18) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 19) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 20) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 21) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Person (line 49) | type Person struct
  function Save (line 64) | func Save(in *Person) (int, error) {
  function LoadAll (line 91) | func LoadAll() ([]*Person, error) {
  function Load (line 131) | func Load(ID int) (*Person, error) {
  type scanner (line 156) | type scanner
  function populatePerson (line 159) | func populatePerson(scanner scanner) (*Person, error) {
  function init (line 165) | func init() {

FILE: ch05/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 14) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 51) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 89) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 116) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 179) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 241) | func convertSQLToRegex(in string) string {

FILE: ch05/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 16) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 19) | defaultPrice = 0.0
  type Converter (line 24) | type Converter struct
    method Do (line 27) | func (c *Converter) Do(basePrice float64, currency string) (float64, e...
    method loadRateFromServer (line 45) | func (c *Converter) loadRateFromServer(currency string) (*http.Respons...
    method extractRate (line 68) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 91) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
  type apiResponseFormat (line 110) | type apiResponseFormat struct

FILE: ch05/acme/internal/modules/get/get.go
  type Getter (line 16) | type Getter struct
    method Do (line 20) | func (g *Getter) Do(ID int) (*data.Person, error) {

FILE: ch05/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 12) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 44) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 72) | func TestGetter_Do_error(t *testing.T) {

FILE: ch05/acme/internal/modules/list/list.go
  type Lister (line 16) | type Lister struct
    method Do (line 20) | func (l *Lister) Do() ([]*data.Person, error) {
    method load (line 36) | func (l *Lister) load() ([]*data.Person, error) {

FILE: ch05/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 12) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 46) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 71) | func TestLister_Do_error(t *testing.T) {

FILE: ch05/acme/internal/modules/register/register.go
  constant defaultPersonID (line 14) | defaultPersonID = 0
  type Registerer (line 43) | type Registerer struct
    method Do (line 47) | func (r *Registerer) Do(in *data.Person) (int, error) {
    method validateInput (line 72) | func (r *Registerer) validateInput(in *data.Person) error {
    method getPrice (line 92) | func (r *Registerer) getPrice(currency string) (float64, error) {
    method save (line 104) | func (r *Registerer) save(in *data.Person, price float64) (int, error) {

FILE: ch05/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 12) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 44) | func TestRegisterer_Do_error(t *testing.T) {

FILE: ch05/acme/internal/rest/common_test.go
  function getOpenPort (line 8) | func getOpenPort() (string, error) {
  function startServer (line 20) | func startServer(ctx context.Context) (string, error) {

FILE: ch05/acme/internal/rest/get.go
  constant defaultPersonID (line 19) | defaultPersonID = 0
  type GetHandler (line 26) | type GetHandler struct
    method ServeHTTP (line 30) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 57) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 81) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
  type getResponseFormat (line 95) | type getResponseFormat struct

FILE: ch05/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 14) | func TestGetHandler_ServeHTTP(t *testing.T) {

FILE: ch05/acme/internal/rest/list.go
  type ListHandler (line 14) | type ListHandler struct
    method ServeHTTP (line 18) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 37) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
  type listResponseFormat (line 54) | type listResponseFormat struct
  type listResponseItemFormat (line 58) | type listResponseItemFormat struct

FILE: ch05/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 14) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch05/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch05/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 12) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch05/acme/internal/rest/register.go
  type RegisterHandler (line 15) | type RegisterHandler struct
    method ServeHTTP (line 19) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 42) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 55) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
  type registerRequest (line 67) | type registerRequest struct

FILE: ch05/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 16) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRequest (line 40) | func buildValidRequest() io.Reader {

FILE: ch05/acme/internal/rest/server.go
  function New (line 10) | func New(address string) *Server {
  type Server (line 21) | type Server struct
    method Listen (line 32) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 54) | func (s *Server) buildRouter() http.Handler {

FILE: ch05/acme/main.go
  function main (line 10) | func main() {

FILE: ch05/fake.go
  function init (line 3) | func init() {

FILE: ch06/01_constructor_injection/01_welcome_email.go
  function NewWelcomeSender (line 7) | func NewWelcomeSender(in *Mailer) (*WelcomeSender, error) {
  function NewWelcomeSenderNoGuard (line 18) | func NewWelcomeSenderNoGuard(in *Mailer) *WelcomeSender {
  type WelcomeSender (line 25) | type WelcomeSender struct
    method Send (line 29) | func (w *WelcomeSender) Send(to string) error {
    method buildMessage (line 36) | func (w *WelcomeSender) buildMessage() string {
  type Mailer (line 41) | type Mailer struct
    method Send (line 48) | func (m *Mailer) Send(to string, body string) error {
    method Receive (line 53) | func (m *Mailer) Receive(address string) (string, error) {

FILE: ch06/01_constructor_injection/01_welcome_email_test.go
  function TestNewWelcomeSender_happyPath (line 9) | func TestNewWelcomeSender_happyPath(t *testing.T) {
  function TestNewWelcomeSender_guardClause (line 15) | func TestNewWelcomeSender_guardClause(t *testing.T) {
  function TestNewWelcomeSenderNoGuard_happyPath (line 21) | func TestNewWelcomeSenderNoGuard_happyPath(t *testing.T) {

FILE: ch06/01_constructor_injection/02_mailer_interface.go
  type MailerInterface (line 4) | type MailerInterface interface

FILE: ch06/01_constructor_injection/03_sender_interface.go
  type Sender (line 3) | type Sender interface
  function NewWelcomeSenderV2 (line 7) | func NewWelcomeSenderV2(in Sender) *WelcomeSenderV2 {
  type WelcomeSenderV2 (line 14) | type WelcomeSenderV2 struct
    method Send (line 18) | func (w *WelcomeSenderV2) Send(to string) error {
    method buildMessage (line 25) | func (w *WelcomeSenderV2) buildMessage() string {

FILE: ch06/01_constructor_injection/05_duck_typing.go
  type Talker (line 7) | type Talker interface
  type Dog (line 12) | type Dog struct
    method Speak (line 14) | func (d Dog) Speak() string {
    method Shout (line 18) | func (d Dog) Shout() string {
  function SpeakExample (line 22) | func SpeakExample() {

FILE: ch06/02_advantages/01_easy_to_implement.go
  type WelcomeSender (line 4) | type WelcomeSender struct
    method Send (line 8) | func (w *WelcomeSender) Send(to string) error {
    method buildMessage (line 15) | func (w *WelcomeSender) buildMessage() string {
  type Mailer (line 20) | type Mailer struct
    method Send (line 22) | func (m *Mailer) Send(to string, body string) error {

FILE: ch06/02_advantages/01_easy_to_implement_example_test.go
  function ExampleWelcomeSender_Send (line 7) | func ExampleWelcomeSender_Send() {

FILE: ch06/02_advantages/02_easy_to_implement.go
  function NewWelcomeSenderV2 (line 3) | func NewWelcomeSenderV2(mailer *Mailer) *WelcomeSenderV2 {
  type WelcomeSenderV2 (line 10) | type WelcomeSenderV2 struct
    method Send (line 14) | func (w *WelcomeSenderV2) Send(to string) error {
    method buildMessage (line 21) | func (w *WelcomeSenderV2) buildMessage() string {

FILE: ch06/02_advantages/02_easy_to_implement_example_test.go
  function ExampleWelcomeSenderV2_Send (line 7) | func ExampleWelcomeSenderV2_Send() {

FILE: ch06/02_advantages/03_predictable.go
  type Engine (line 7) | type Engine interface
  type Car (line 15) | type Car struct
    method Drive (line 19) | func (c *Car) Drive() error {
    method Stop (line 31) | func (c *Car) Stop() error {

FILE: ch06/02_advantages/04_predictable.go
  function NewCarV2 (line 7) | func NewCarV2(engine Engine) (*CarV2, error) {
  type CarV2 (line 17) | type CarV2 struct
    method Drive (line 21) | func (c *CarV2) Drive() error {
    method Stop (line 29) | func (c *CarV2) Stop() error {

FILE: ch06/02_advantages/05_encapsulation.go
  method FillPetrolTank (line 7) | func (c *CarV2) FillPetrolTank() error {
  method fill (line 17) | func (c CarV2) fill() error {

FILE: ch06/02_advantages/06_encapsulation.go
  method FillPetrolTankV2 (line 7) | func (c *CarV2) FillPetrolTankV2(engine Engine) error {

FILE: ch06/03_applying/01/01_register_handler_before.go
  type RegisterHandler (line 15) | type RegisterHandler struct
    method ServeHTTP (line 19) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 42) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 55) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
  type registerRequest (line 67) | type registerRequest struct

FILE: ch06/03_applying/01/data/person.go
  type Person (line 4) | type Person struct

FILE: ch06/03_applying/01/register/register.go
  type Registerer (line 13) | type Registerer struct
    method Do (line 17) | func (r *Registerer) Do(in *data.Person) (int, error) {

FILE: ch06/03_applying/02/01_register_handler.go
  type RegisterHandler (line 10) | type RegisterHandler struct

FILE: ch06/03_applying/02/data/person.go
  type Person (line 4) | type Person struct

FILE: ch06/03_applying/02/register/register.go
  type Registerer (line 13) | type Registerer struct
    method Do (line 17) | func (r *Registerer) Do(in *data.Person) (int, error) {

FILE: ch06/03_applying/03/data/person.go
  type Person (line 4) | type Person struct

FILE: ch06/03_applying/03/mock_register_model_test.go
  type MockRegisterModel (line 13) | type MockRegisterModel struct
    method Do (line 18) | func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {

FILE: ch06/03_applying/03/register_test.go
  function TestRegisterHandler_ServeHTTP (line 8) | func TestRegisterHandler_ServeHTTP(t *testing.T) {

FILE: ch06/03_applying/04/data/person.go
  type Person (line 4) | type Person struct

FILE: ch06/03_applying/04/mock_register_model_test.go
  type MockRegisterModel (line 13) | type MockRegisterModel struct
    method Do (line 18) | func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {

FILE: ch06/03_applying/04/register.go
  type RegisterModel (line 10) | type RegisterModel interface
  type RegisterHandler (line 17) | type RegisterHandler struct
    method ServeHTTP (line 22) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
  type registerRequest (line 27) | type registerRequest struct

FILE: ch06/03_applying/04/register_test.go
  function TestRegisterHandler_ServeHTTP (line 15) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRequest (line 54) | func buildValidRequest() io.Reader {

FILE: ch06/03_applying/05/data/person.go
  type Person (line 4) | type Person struct

FILE: ch06/03_applying/05/fakes.go
  function notFoundHandler (line 9) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
  type Server (line 16) | type Server struct
  function NewGetHandler (line 25) | func NewGetHandler(_ GetModel) *GetHandler {
  type GetModel (line 29) | type GetModel interface
  type GetHandler (line 33) | type GetHandler struct
    method ServeHTTP (line 35) | func (g *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
  function NewListHandler (line 37) | func NewListHandler(_ ListModel) *ListHandler {
  type ListModel (line 41) | type ListModel interface
  type ListHandler (line 45) | type ListHandler struct
    method ServeHTTP (line 48) | func (l *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
  function NewRegisterHandler (line 50) | func NewRegisterHandler(_ RegisterModel) *RegisterHandler {
  type RegisterModel (line 54) | type RegisterModel interface
  type RegisterHandler (line 58) | type RegisterHandler struct
    method ServeHTTP (line 61) | func (r *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...

FILE: ch06/03_applying/05/get/getter.go
  type Getter (line 8) | type Getter struct
    method Do (line 10) | func (g *Getter) Do(ID int) (*data.Person, error) {

FILE: ch06/03_applying/05/list/lister.go
  type Lister (line 8) | type Lister struct
    method Do (line 10) | func (l *Lister) Do() ([]*data.Person, error) {

FILE: ch06/03_applying/05/register/registerer.go
  type Registerer (line 8) | type Registerer struct
    method Do (line 10) | func (r *Registerer) Do(in *data.Person) (int, error) {

FILE: ch06/03_applying/05/server.go
  function New (line 10) | func New(address string) *Server {

FILE: ch06/04_disadvantages/01_lots_of_changes.go
  function DealCards (line 4) | func DealCards() (player1 []Card, player2 []Card) {
  function newDeck (line 22) | func newDeck() []Card {
  type Shuffler (line 29) | type Shuffler interface
  type Card (line 34) | type Card struct
  type myShuffler (line 40) | type myShuffler struct
    method Shuffle (line 43) | func (m *myShuffler) Shuffle(cards []Card) {

FILE: ch06/04_disadvantages/02_overuse.go
  constant downstreamServer (line 9) | downstreamServer = "http://www.example.com"
  type FetchRates (line 12) | type FetchRates struct
    method Fetch (line 14) | func (f *FetchRates) Fetch() ([]Rate, error) {
  type downstreamResponse (line 48) | type downstreamResponse struct
  type Rate (line 52) | type Rate struct

FILE: ch06/04_disadvantages/03_non_obvious.go
  function NewClient (line 8) | func NewClient(service DepService) Client {
  type Client (line 15) | type Client interface
  type clientImpl (line 20) | type clientImpl struct
    method DoSomethingUseful (line 24) | func (c *clientImpl) DoSomethingUseful() (bool, error) {
  type DepService (line 29) | type DepService interface

FILE: ch06/04_disadvantages/04_non_obvious_example_test.go
  function Example (line 3) | func Example() {
  type StubClient (line 8) | type StubClient struct
    method DoSomethingUseful (line 11) | func (s *StubClient) DoSomethingUseful() (bool, error) {

FILE: ch06/04_disadvantages/05_constructors.go
  type InnerService (line 3) | type InnerService struct
  function NewInnerService (line 7) | func NewInnerService(innerDep Dependency) *InnerService {
  type OuterService (line 13) | type OuterService struct
  function NewOuterService (line 20) | func NewOuterService(outerDep Dependency, innerDep Dependency) *OuterSer...
  type Dependency (line 28) | type Dependency interface

FILE: ch06/acme/internal/config/config.go
  constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 18) | type Config struct
  function init (line 36) | func init() {
  function load (line 46) | func load(filename string) error {

FILE: ch06/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch06/acme/internal/logging/logging.go
  type LoggerStdOut (line 11) | type LoggerStdOut struct
    method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch06/acme/internal/modules/data/data.go
  constant defaultPersonID (line 15) | defaultPersonID = 0
  constant sqlAllColumns (line 18) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 19) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 20) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 21) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Person (line 49) | type Person struct
  function Save (line 64) | func Save(in *Person) (int, error) {
  function LoadAll (line 91) | func LoadAll() ([]*Person, error) {
  function Load (line 131) | func Load(ID int) (*Person, error) {
  type scanner (line 156) | type scanner
  function populatePerson (line 159) | func populatePerson(scanner scanner) (*Person, error) {
  function init (line 165) | func init() {

FILE: ch06/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 14) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 51) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 89) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 116) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 179) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 241) | func convertSQLToRegex(in string) string {

FILE: ch06/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 16) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 19) | defaultPrice = 0.0
  type Converter (line 24) | type Converter struct
    method Do (line 27) | func (c *Converter) Do(basePrice float64, currency string) (float64, e...
    method loadRateFromServer (line 45) | func (c *Converter) loadRateFromServer(currency string) (*http.Respons...
    method extractRate (line 68) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 91) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
  type apiResponseFormat (line 110) | type apiResponseFormat struct

FILE: ch06/acme/internal/modules/get/get.go
  type Getter (line 16) | type Getter struct
    method Do (line 20) | func (g *Getter) Do(ID int) (*data.Person, error) {

FILE: ch06/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 12) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 44) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 72) | func TestGetter_Do_error(t *testing.T) {

FILE: ch06/acme/internal/modules/list/list.go
  type Lister (line 16) | type Lister struct
    method Do (line 20) | func (l *Lister) Do() ([]*data.Person, error) {
    method load (line 36) | func (l *Lister) load() ([]*data.Person, error) {

FILE: ch06/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 12) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 46) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 71) | func TestLister_Do_error(t *testing.T) {

FILE: ch06/acme/internal/modules/register/register.go
  constant defaultPersonID (line 14) | defaultPersonID = 0
  type Registerer (line 43) | type Registerer struct
    method Do (line 47) | func (r *Registerer) Do(in *data.Person) (int, error) {
    method validateInput (line 72) | func (r *Registerer) validateInput(in *data.Person) error {
    method getPrice (line 92) | func (r *Registerer) getPrice(currency string) (float64, error) {
    method save (line 104) | func (r *Registerer) save(in *data.Person, price float64) (int, error) {

FILE: ch06/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 12) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 44) | func TestRegisterer_Do_error(t *testing.T) {

FILE: ch06/acme/internal/rest/get.go
  constant defaultPersonID (line 18) | defaultPersonID = 0
  constant muxVarID (line 21) | muxVarID = "id"
  type GetModel (line 26) | type GetModel interface
  function NewGetHandler (line 31) | func NewGetHandler(model GetModel) *GetHandler {
  type GetHandler (line 41) | type GetHandler struct
    method ServeHTTP (line 46) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 72) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 96) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
  type getResponseFormat (line 110) | type getResponseFormat struct

FILE: ch06/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 17) | func TestGetHandler_ServeHTTP(t *testing.T) {

FILE: ch06/acme/internal/rest/list.go
  type ListModel (line 13) | type ListModel interface
  function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
  type ListHandler (line 26) | type ListHandler struct
    method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
  type listResponseFormat (line 66) | type listResponseFormat struct
  type listResponseItemFormat (line 70) | type listResponseItemFormat struct

FILE: ch06/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch06/acme/internal/rest/mock_get_model_test.go
  type MockGetModel (line 13) | type MockGetModel struct
    method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {

FILE: ch06/acme/internal/rest/mock_list_model_test.go
  type MockListModel (line 13) | type MockListModel struct
    method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {

FILE: ch06/acme/internal/rest/mock_register_model_test.go
  type MockRegisterModel (line 13) | type MockRegisterModel struct
    method Do (line 18) | func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {

FILE: ch06/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch06/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch06/acme/internal/rest/register.go
  type RegisterModel (line 13) | type RegisterModel interface
  function NewRegisterHandler (line 18) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
  type RegisterHandler (line 27) | type RegisterHandler struct
    method ServeHTTP (line 32) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 55) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 68) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
  type registerRequest (line 79) | type registerRequest struct

FILE: ch06/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {

FILE: ch06/acme/internal/rest/server.go
  function New (line 10) | func New(address string,
  type Server (line 25) | type Server struct
    method Listen (line 36) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 58) | func (s *Server) buildRouter() http.Handler {

FILE: ch06/acme/main.go
  function main (line 13) | func main() {

FILE: ch06/fake.go
  function init (line 3) | func init() {

FILE: ch07/01_method_injection/01_fprint.go
  function ExampleA (line 8) | func ExampleA() {

FILE: ch07/01_method_injection/02_http_request.go
  function ExampleB (line 9) | func ExampleB() {

FILE: ch07/01_method_injection/03_fprint.go
  function Fprint (line 9) | func Fprint(w io.Writer, a ...interface{}) (n int, err error) {

FILE: ch07/01_method_injection/04_http_request.go
  function NewRequest (line 10) | func NewRequest(method, url string, body io.Reader) (*http.Request, erro...
  function validateMethod (line 40) | func validateMethod(method string) (string, error) {
  function validateURL (line 44) | func validateURL(url string) (*url.URL, error) {

FILE: ch07/01_method_injection/05_timestamp_writer_v1.go
  function TimeStampWriterV1 (line 10) | func TimeStampWriterV1(writer io.Writer, message string) {

FILE: ch07/01_method_injection/06_timestamp_writer_v2.go
  function TimeStampWriterV2 (line 11) | func TimeStampWriterV2(writer io.Writer, message string) error {

FILE: ch07/01_method_injection/07_timestamp_writer_v3.go
  function TimeStampWriterV3 (line 11) | func TimeStampWriterV3(writer io.Writer, message string) {

FILE: ch07/02_advantages/01_handler_v1.go
  function HandlerV1 (line 8) | func HandlerV1(response http.ResponseWriter, request *http.Request) {
  type Animal (line 25) | type Animal struct

FILE: ch07/02_advantages/02_handler_v2.go
  function HandlerV2 (line 8) | func HandlerV2(response http.ResponseWriter, request *http.Request) {
  function outputAnimal (line 18) | func outputAnimal(response http.ResponseWriter, animal *Animal) {

FILE: ch07/02_advantages/03_handler_v3.go
  function HandlerV3 (line 8) | func HandlerV3(response http.ResponseWriter, request *http.Request) {
  function outputJSON (line 18) | func outputJSON(response http.ResponseWriter, data interface{}) {

FILE: ch07/02_advantages/04_context_influence.go
  function WriteLog (line 9) | func WriteLog(writer io.Writer, message string) error {
  function Usage (line 14) | func Usage() {

FILE: ch07/02_advantages/05_person_loader.go
  type OrderLoader (line 16) | type OrderLoader interface
  function NewLoadOrderHandler (line 21) | func NewLoadOrderHandler(loader OrderLoader) *LoadOrderHandler {
  type LoadOrderHandler (line 28) | type LoadOrderHandler struct
    method ServeHTTP (line 33) | func (l *LoadOrderHandler) ServeHTTP(response http.ResponseWriter, req...
    method authenticateUser (line 115) | func (l *LoadOrderHandler) authenticateUser(request *http.Request) (*U...
    method extractOrderID (line 120) | func (l *LoadOrderHandler) extractOrderID(request *http.Request) (int,...
  type AuthenticatedLoader (line 68) | type AuthenticatedLoader struct
    method loadByOwner (line 74) | func (a *AuthenticatedLoader) loadByOwner(owner Owner, orderID int) (*...
    method load (line 89) | func (a *AuthenticatedLoader) load(orderID int) (*Order, error) {
  type Owner (line 94) | type Owner interface
  type Order (line 98) | type Order struct
  type User (line 104) | type User struct
    method ID (line 110) | func (u *User) ID() int {

FILE: ch07/04_disadvantages/01_data_struct.go
  type PersonLoader (line 9) | type PersonLoader struct
    method Load (line 12) | func (d *PersonLoader) Load(db *sql.DB, ID int) (*Person, error) {
    method LoadAll (line 16) | func (d *PersonLoader) LoadAll(db *sql.DB) ([]*Person, error) {
  type Person (line 20) | type Person struct

FILE: ch07/04_disadvantages/02_ux_improvement.go
  type MyPersonLoader (line 3) | type MyPersonLoader interface

FILE: ch07/04_disadvantages/03_many_params.go
  type Generator (line 7) | type Generator struct
    method Generate (line 9) | func (g *Generator) Generate(storage Storage, template io.Reader, dest...
  type Storage (line 13) | type Storage interface
  type Renderer (line 17) | type Renderer interface
  type Formatter (line 21) | type Formatter interface

FILE: ch07/04_disadvantages/04_many_params_v2.go
  function NewGeneratorV2 (line 7) | func NewGeneratorV2(storage Storage, renderer Renderer, formatter Format...
  type GeneratorV2 (line 15) | type GeneratorV2 struct
    method Generate (line 21) | func (g *GeneratorV2) Generate(template io.Reader, destination io.Writ...

FILE: ch07/acme/internal/config/config.go
  constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 18) | type Config struct
  function init (line 36) | func init() {
  function load (line 46) | func load(filename string) error {

FILE: ch07/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch07/acme/internal/logging/logging.go
  type LoggerStdOut (line 11) | type LoggerStdOut struct
    method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch07/acme/internal/modules/data/data.go
  constant defaultPersonID (line 17) | defaultPersonID = 0
  constant sqlAllColumns (line 20) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 21) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 22) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 23) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Person (line 51) | type Person struct
  function Save (line 66) | func Save(ctx context.Context, in *Person) (int, error) {
  function LoadAll (line 97) | func LoadAll(ctx context.Context) ([]*Person, error) {
  function Load (line 141) | func Load(ctx context.Context, ID int) (*Person, error) {
  type scanner (line 170) | type scanner
  function populatePerson (line 173) | func populatePerson(scanner scanner) (*Person, error) {
  function init (line 179) | func init() {

FILE: ch07/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 16) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 57) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 99) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 130) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 197) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 263) | func convertSQLToRegex(in string) string {

FILE: ch07/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 18) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 21) | defaultPrice = 0.0
  type Converter (line 26) | type Converter struct
    method Do (line 29) | func (c *Converter) Do(ctx context.Context, basePrice float64, currenc...
    method loadRateFromServer (line 47) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
    method extractRate (line 84) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 107) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
  type apiResponseFormat (line 126) | type apiResponseFormat struct

FILE: ch07/acme/internal/modules/get/get.go
  type Getter (line 17) | type Getter struct
    method Do (line 21) | func (g *Getter) Do(ID int) (*data.Person, error) {

FILE: ch07/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 45) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 73) | func TestGetter_Do_error(t *testing.T) {

FILE: ch07/acme/internal/modules/list/list.go
  type Lister (line 17) | type Lister struct
    method Do (line 21) | func (l *Lister) Do() ([]*data.Person, error) {
    method load (line 37) | func (l *Lister) load() ([]*data.Person, error) {

FILE: ch07/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 47) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 72) | func TestLister_Do_error(t *testing.T) {

FILE: ch07/acme/internal/modules/register/register.go
  constant defaultPersonID (line 15) | defaultPersonID = 0
  type Registerer (line 44) | type Registerer struct
    method Do (line 48) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
    method validateInput (line 73) | func (r *Registerer) validateInput(in *data.Person) error {
    method getPrice (line 93) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
    method save (line 105) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...

FILE: ch07/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 14) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 50) | func TestRegisterer_Do_error(t *testing.T) {

FILE: ch07/acme/internal/rest/get.go
  constant defaultPersonID (line 18) | defaultPersonID = 0
  constant muxVarID (line 21) | muxVarID = "id"
  type GetModel (line 26) | type GetModel interface
  function NewGetHandler (line 31) | func NewGetHandler(model GetModel) *GetHandler {
  type GetHandler (line 41) | type GetHandler struct
    method ServeHTTP (line 46) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 72) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 96) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
  type getResponseFormat (line 110) | type getResponseFormat struct

FILE: ch07/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 17) | func TestGetHandler_ServeHTTP(t *testing.T) {

FILE: ch07/acme/internal/rest/list.go
  type ListModel (line 13) | type ListModel interface
  function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
  type ListHandler (line 26) | type ListHandler struct
    method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
  type listResponseFormat (line 66) | type listResponseFormat struct
  type listResponseItemFormat (line 70) | type listResponseItemFormat struct

FILE: ch07/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch07/acme/internal/rest/mock_get_model_test.go
  type MockGetModel (line 13) | type MockGetModel struct
    method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {

FILE: ch07/acme/internal/rest/mock_list_model_test.go
  type MockListModel (line 13) | type MockListModel struct
    method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {

FILE: ch07/acme/internal/rest/mock_register_model_test.go
  type MockRegisterModel (line 15) | type MockRegisterModel struct
    method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...

FILE: ch07/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch07/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch07/acme/internal/rest/register.go
  type RegisterModel (line 15) | type RegisterModel interface
  function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
  type RegisterHandler (line 29) | type RegisterHandler struct
    method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
  type registerRequest (line 85) | type registerRequest struct

FILE: ch07/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {

FILE: ch07/acme/internal/rest/server.go
  function New (line 10) | func New(address string,
  type Server (line 25) | type Server struct
    method Listen (line 36) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 58) | func (s *Server) buildRouter() http.Handler {

FILE: ch07/acme/main.go
  function main (line 13) | func main() {

FILE: ch07/fake.go
  function init (line 3) | func init() {

FILE: ch08/01_config_injection/01_long_constructor.go
  function NewLongConstructor (line 8) | func NewLongConstructor(logger Logger, stats Instrumentation, limiter Ra...
  type MyStruct (line 15) | type MyStruct struct
  type Logger (line 19) | type Logger interface
  type Instrumentation (line 27) | type Instrumentation interface
  type RateLimiter (line 33) | type RateLimiter interface
  type Cache (line 39) | type Cache interface

FILE: ch08/01_config_injection/02_by_config_example.go
  function NewByConfigConstructor (line 8) | func NewByConfigConstructor(cfg MyConfig, limiter RateLimiter, cache Cac...
  type MyConfig (line 15) | type MyConfig interface

FILE: ch08/01_config_injection/03_shared_params.go
  function Usage (line 8) | func Usage() {
  type FetcherConfig (line 17) | type FetcherConfig interface
  function NewFetcher (line 22) | func NewFetcher(cfg FetcherConfig, url string, timeout time.Duration) *M...
  type MyObject (line 26) | type MyObject struct
  type fakeConfig (line 29) | type fakeConfig struct
    method Logger (line 32) | func (f *fakeConfig) Logger() Logger {
    method Instrumentation (line 37) | func (f *fakeConfig) Instrumentation() Instrumentation {
    method URL (line 41) | func (f *fakeConfig) URL() string {
    method Timeout (line 45) | func (f *fakeConfig) Timeout() time.Duration {

FILE: ch08/02_advantages/01_injected_config/01.go
  function NewMyObject (line 7) | func NewMyObject(cfg *config.Config) *MyObject {
  type MyObject (line 13) | type MyObject struct
    method Do (line 17) | func (m *MyObject) Do() (interface{}, error) {

FILE: ch08/02_advantages/01_injected_config/01_test.go
  constant testConfigLocation (line 12) | testConfigLocation = ""
  function TestInjectedConfig (line 15) | func TestInjectedConfig(t *testing.T) {

FILE: ch08/02_advantages/02_config_injection/02.go
  function NewMyObject (line 8) | func NewMyObject(cfg Config) *MyObject {
  type Config (line 14) | type Config interface
  type MyObject (line 19) | type MyObject struct
    method Do (line 23) | func (m *MyObject) Do() (interface{}, error) {

FILE: ch08/02_advantages/02_config_injection/02_test.go
  function TestConfigInjection (line 11) | func TestConfigInjection(t *testing.T) {
  type TestConfig (line 25) | type TestConfig struct
    method Logger (line 30) | func (t *TestConfig) Logger() *logging.Logger {
    method Stats (line 34) | func (t *TestConfig) Stats() *stats.Collector {

FILE: ch08/02_advantages/03_long_constructor.go
  function NewLongConstructor (line 8) | func NewLongConstructor(logger Logger, stats Instrumentation, limiter Ra...
  type MyStruct (line 15) | type MyStruct struct
  type Logger (line 19) | type Logger interface
  type Instrumentation (line 27) | type Instrumentation interface
  type RateLimiter (line 33) | type RateLimiter interface
  type Cache (line 39) | type Cache interface

FILE: ch08/02_advantages/04_by_config_example.go
  function NewByConfigConstructor (line 4) | func NewByConfigConstructor(cfg MyConfig, url string, credentials string...
  type MyConfig (line 11) | type MyConfig interface

FILE: ch08/02_advantages/config/config.go
  function LoadFromFile (line 11) | func LoadFromFile(path string) (*Config, error) {
  type Config (line 17) | type Config struct
    method Logger (line 32) | func (c *Config) Logger() *logging.Logger {
    method Stats (line 43) | func (c *Config) Stats() *stats.Collector {

FILE: ch08/02_advantages/logging/logger.go
  type Logger (line 4) | type Logger struct
    method Error (line 9) | func (l *Logger) Error(message string, args ...interface{}) {
    method Warn (line 14) | func (l *Logger) Warn(message string, args ...interface{}) {
    method Info (line 19) | func (l *Logger) Info(message string, args ...interface{}) {
    method Debug (line 24) | func (l *Logger) Debug(message string, args ...interface{}) {

FILE: ch08/02_advantages/stats/stats.go
  type Collector (line 8) | type Collector struct
    method Count (line 13) | func (c *Collector) Count(key string, value int) {
    method Duration (line 18) | func (c *Collector) Duration(key string, start time.Time) {

FILE: ch08/03_applying/01_define_register_config.go
  type Config (line 10) | type Config interface

FILE: ch08/03_applying/02_register_with_config_injection.go
  function NewRegisterer (line 13) | func NewRegisterer(cfg Config) *Registerer {
  type Config (line 20) | type Config interface
  type Registerer (line 31) | type Registerer struct
    method getPrice (line 36) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
    method logger (line 47) | func (r *Registerer) logger() logging.Logger {

FILE: ch08/03_applying/03_model_before_data_changes.go
  type GetterConfig (line 13) | type GetterConfig interface
  type Getter (line 19) | type Getter struct
    method Do (line 24) | func (g *Getter) Do(ID int) (*data.Person, error) {

FILE: ch08/03_applying/04_test_config_link_to_config_package.go
  type testConfig (line 10) | type testConfig struct
    method Logger (line 13) | func (t *testConfig) Logger() logging.Logger {
    method RegistrationBasePrice (line 18) | func (t *testConfig) RegistrationBasePrice() float64 {
    method DataDSN (line 23) | func (t *testConfig) DataDSN() string {
    method ExchangeBaseURL (line 28) | func (t *testConfig) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 33) | func (t *testConfig) ExchangeAPIKey() string {

FILE: ch08/03_applying/06_simple_test_server.go
  type happyExchangeRateService (line 9) | type happyExchangeRateService struct
    method ServeHTTP (line 12) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...

FILE: ch08/04_disadvantages/01_leaking_details.go
  type PeopleFilterConfig (line 8) | type PeopleFilterConfig interface
  function PeopleFilter (line 12) | func PeopleFilter(cfg PeopleFilterConfig, filter string) ([]Person, erro...
  type PersonLoaderConfig (line 31) | type PersonLoaderConfig interface
  type PersonLoader (line 35) | type PersonLoader struct
    method LoadAll (line 37) | func (p *PersonLoader) LoadAll(cfg PersonLoaderConfig) ([]Person, erro...
  type Person (line 42) | type Person struct

FILE: ch08/04_disadvantages/02_hiding_details.go
  type Loader (line 7) | type Loader interface
  function PeopleFilterV2 (line 11) | func PeopleFilterV2(loader Loader, filter string) ([]Person, error) {

FILE: ch08/04_disadvantages/03_unclear_lifecycle.go
  function DoJob (line 8) | func DoJob(pool WorkerPool, job Job) error {
  type WorkerPool (line 25) | type WorkerPool interface
  type Worker (line 31) | type Worker interface
  type Job (line 36) | type Job interface

FILE: ch08/04_disadvantages/04_clear_lifecycle.go
  function DoJobUpdated (line 3) | func DoJobUpdated(pool WorkerPool, job Job) error {

FILE: ch08/04_disadvantages/05_layers.go
  function NewLayer1Object (line 3) | func NewLayer1Object(config Layer1Config) *Layer1Object {
  type Layer1Config (line 11) | type Layer1Config interface
  type Layer1Object (line 16) | type Layer1Object struct
  type Layer2Config (line 22) | type Layer2Config interface
  type Layer2Object (line 27) | type Layer2Object struct
  function NewLayer2Object (line 31) | func NewLayer2Object(config Layer2Config) *Layer2Object {
  type Logger (line 38) | type Logger interface

FILE: ch08/acme/internal/config/config.go
  constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 18) | type Config struct
    method Logger (line 39) | func (c *Config) Logger() logging.Logger {
    method RegistrationBasePrice (line 48) | func (c *Config) RegistrationBasePrice() float64 {
    method DataDSN (line 53) | func (c *Config) DataDSN() string {
    method ExchangeBaseURL (line 58) | func (c *Config) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 63) | func (c *Config) ExchangeAPIKey() string {
    method BindAddress (line 68) | func (c *Config) BindAddress() string {
  function init (line 73) | func init() {
  function load (line 83) | func load(filename string) error {

FILE: ch08/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch08/acme/internal/logging/logging.go
  type Logger (line 8) | type Logger interface
  type LoggerStdOut (line 19) | type LoggerStdOut struct
    method Debug (line 22) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 27) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 32) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 37) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch08/acme/internal/modules/data/data.go
  constant defaultPersonID (line 16) | defaultPersonID = 0
  constant sqlAllColumns (line 19) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 20) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 21) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 22) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Config (line 33) | type Config interface
  type Person (line 55) | type Person struct
  function Save (line 70) | func Save(ctx context.Context, cfg Config, in *Person) (int, error) {
  function LoadAll (line 101) | func LoadAll(ctx context.Context, cfg Config) ([]*Person, error) {
  function Load (line 145) | func Load(ctx context.Context, cfg Config, ID int) (*Person, error) {
  type scanner (line 174) | type scanner
  function populatePerson (line 177) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch08/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 52) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 88) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 119) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 186) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 252) | func convertSQLToRegex(in string) string {
  type testConfig (line 256) | type testConfig struct
    method Logger (line 259) | func (t *testConfig) Logger() logging.Logger {
    method DataDSN (line 264) | func (t *testConfig) DataDSN() string {

FILE: ch08/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 20) | defaultPrice = 0.0
  function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
  type Config (line 31) | type Config interface
  type Converter (line 39) | type Converter struct
    method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
    method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
    method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
    method logger (line 140) | func (c *Converter) logger() logging.Logger {
  type apiResponseFormat (line 145) | type apiResponseFormat struct

FILE: ch08/acme/internal/modules/exchange/converter_ext_bounday_test.go
  function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {

FILE: ch08/acme/internal/modules/exchange/converter_int_bounday_test.go
  function TestInternalBoundaryTest (line 13) | func TestInternalBoundaryTest(t *testing.T) {
  type happyExchangeRateService (line 33) | type happyExchangeRateService struct
    method ServeHTTP (line 36) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
  type testConfig (line 52) | type testConfig struct
    method Logger (line 58) | func (t *testConfig) Logger() logging.Logger {
    method ExchangeBaseURL (line 63) | func (t *testConfig) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 68) | func (t *testConfig) ExchangeAPIKey() string {

FILE: ch08/acme/internal/modules/get/get.go
  function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
  type Config (line 24) | type Config interface
  type Getter (line 31) | type Getter struct
    method Do (line 36) | func (g *Getter) Do(ID int) (*data.Person, error) {

FILE: ch08/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 45) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 73) | func TestGetter_Do_error(t *testing.T) {

FILE: ch08/acme/internal/modules/list/list.go
  function NewLister (line 17) | func NewLister(cfg Config) *Lister {
  type Config (line 24) | type Config interface
  type Lister (line 31) | type Lister struct
    method Do (line 36) | func (l *Lister) Do() ([]*data.Person, error) {
    method load (line 52) | func (l *Lister) load() ([]*data.Person, error) {

FILE: ch08/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 47) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 72) | func TestLister_Do_error(t *testing.T) {

FILE: ch08/acme/internal/modules/register/register.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
  type Exchanger (line 45) | type Exchanger interface
  type Config (line 51) | type Config interface
  type Registerer (line 63) | type Registerer struct
    method Do (line 69) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
    method validateInput (line 94) | func (r *Registerer) validateInput(in *data.Person) error {
    method getPrice (line 114) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
    method save (line 125) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
    method logger (line 135) | func (r *Registerer) logger() logging.Logger {

FILE: ch08/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 15) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 54) | func TestRegisterer_Do_error(t *testing.T) {
  type testConfig (line 94) | type testConfig struct
    method Logger (line 97) | func (t *testConfig) Logger() logging.Logger {
    method RegistrationBasePrice (line 102) | func (t *testConfig) RegistrationBasePrice() float64 {
    method DataDSN (line 107) | func (t *testConfig) DataDSN() string {
  type stubExchanger (line 111) | type stubExchanger struct
    method Exchange (line 114) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...

FILE: ch08/acme/internal/rest/get.go
  constant defaultPersonID (line 18) | defaultPersonID = 0
  constant muxVarID (line 21) | muxVarID = "id"
  type GetModel (line 26) | type GetModel interface
  type GetConfig (line 31) | type GetConfig interface
  function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
  type GetHandler (line 47) | type GetHandler struct
    method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
  type getResponseFormat (line 117) | type getResponseFormat struct

FILE: ch08/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
  type testConfig (line 146) | type testConfig struct
    method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
    method BindAddress (line 153) | func (*testConfig) BindAddress() string {

FILE: ch08/acme/internal/rest/list.go
  type ListModel (line 13) | type ListModel interface
  function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
  type ListHandler (line 26) | type ListHandler struct
    method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
  type listResponseFormat (line 66) | type listResponseFormat struct
  type listResponseItemFormat (line 70) | type listResponseItemFormat struct

FILE: ch08/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch08/acme/internal/rest/mock_get_model_test.go
  type MockGetModel (line 13) | type MockGetModel struct
    method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {

FILE: ch08/acme/internal/rest/mock_list_model_test.go
  type MockListModel (line 13) | type MockListModel struct
    method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {

FILE: ch08/acme/internal/rest/mock_register_model_test.go
  type MockRegisterModel (line 15) | type MockRegisterModel struct
    method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...

FILE: ch08/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch08/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch08/acme/internal/rest/register.go
  type RegisterModel (line 15) | type RegisterModel interface
  function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
  type RegisterHandler (line 29) | type RegisterHandler struct
    method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
  type registerRequest (line 85) | type registerRequest struct

FILE: ch08/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {

FILE: ch08/acme/internal/rest/server.go
  type Config (line 11) | type Config interface
  function New (line 17) | func New(cfg Config,
  type Server (line 32) | type Server struct
    method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {

FILE: ch08/acme/main.go
  function main (line 14) | func main() {

FILE: ch08/fake.go
  function init (line 3) | func init() {

FILE: ch09/01_jit_injection/01_injecting_db.go
  function NewMyLoadPersonLogic (line 3) | func NewMyLoadPersonLogic(ds DataSource) *MyLoadPersonLogic {
  type MyLoadPersonLogic (line 9) | type MyLoadPersonLogic struct
    method Load (line 14) | func (m *MyLoadPersonLogic) Load(ID int) (Person, error) {
  type DataSource (line 18) | type DataSource interface
  type Person (line 23) | type Person struct

FILE: ch09/01_jit_injection/01_injecting_db_test.go
  function TestMyLoadPersonLogic (line 9) | func TestMyLoadPersonLogic(t *testing.T) {
  type mockDB (line 25) | type mockDB struct
    method Load (line 31) | func (m *mockDB) Load(ID int) (Person, error) {

FILE: ch09/01_jit_injection/02_injecting_business_logic.go
  function NewLoadPersonHandler (line 8) | func NewLoadPersonHandler(logic LoadPersonLogic) *LoadPersonHandler {
  type LoadPersonHandler (line 14) | type LoadPersonHandler struct
    method ServeHTTP (line 18) | func (h *LoadPersonHandler) ServeHTTP(response http.ResponseWriter, re...
    method extractInputFromRequest (line 31) | func (h *LoadPersonHandler) extractInputFromRequest(request *http.Requ...
    method writeOutput (line 36) | func (h *LoadPersonHandler) writeOutput(writer http.ResponseWriter, pe...
  type LoadPersonLogic (line 40) | type LoadPersonLogic interface

FILE: ch09/01_jit_injection/03_injecting_db_jit.go
  type MyLoadPersonLogicJIT (line 7) | type MyLoadPersonLogicJIT struct
    method Load (line 12) | func (m *MyLoadPersonLogicJIT) Load(ID int) (Person, error) {
    method getDataSource (line 16) | func (m *MyLoadPersonLogicJIT) getDataSource() DataSourceJIT {
  type DataSourceJIT (line 24) | type DataSourceJIT interface
  function NewMyDataSourceJIT (line 29) | func NewMyDataSourceJIT() *MyDataSourceJIT {
  type MyDataSourceJIT (line 34) | type MyDataSourceJIT struct
    method Load (line 37) | func (m *MyDataSourceJIT) Load(ID int) (Person, error) {

FILE: ch09/01_jit_injection/03_injecting_db_jit_test.go
  function TestMyLoadPersonLogicJIT (line 9) | func TestMyLoadPersonLogicJIT(t *testing.T) {

FILE: ch09/01_jit_injection/04_noop_debugger.go
  type ObjectWithDebugger (line 7) | type ObjectWithDebugger struct
    method DoSomethingAmazing (line 11) | func (o *ObjectWithDebugger) DoSomethingAmazing(input int) error {
    method getDebugger (line 20) | func (o *ObjectWithDebugger) getDebugger() Debugger {
    method doSomething (line 28) | func (o *ObjectWithDebugger) doSomething() error {
  type Debugger (line 32) | type Debugger interface
  type noopDebugger (line 37) | type noopDebugger struct
    method Log (line 42) | func (n *noopDebugger) Log(_ string, args ...interface{}) {

FILE: ch09/02_advantages/01_long_constructor.go
  function NewGenerator (line 7) | func NewGenerator(storage Storage, renderer Renderer, template io.Reader...
  type Generator (line 15) | type Generator struct
    method Generate (line 21) | func (g *Generator) Generate(destination io.Writer, params ...interfac...
  type Storage (line 25) | type Storage interface
  type Renderer (line 29) | type Renderer interface

FILE: ch09/02_advantages/02_short_constructor.go
  function NewGeneratorV2 (line 7) | func NewGeneratorV2(template io.Reader) *Generator {
  method getStorage (line 13) | func (g *Generator) getStorage() Storage {
  method getRenderer (line 20) | func (g *Generator) getRenderer() Renderer {
  type DefaultStorage (line 28) | type DefaultStorage struct
    method Load (line 31) | func (d *DefaultStorage) Load() []interface{} {
  type DefaultRenderer (line 36) | type DefaultRenderer struct
    method Render (line 39) | func (d *DefaultRenderer) Render(template io.Reader, params ...interfa...

FILE: ch09/02_advantages/03_optional_dep_without_jitdi.go
  function NewLoaderWithoutJIT (line 3) | func NewLoaderWithoutJIT(ds Datastore) *LoaderWithoutJIT {
  type LoaderWithoutJIT (line 9) | type LoaderWithoutJIT struct
    method Load (line 17) | func (l *LoaderWithoutJIT) Load(ID int) (*Animal, error) {
  type Cache (line 45) | type Cache interface
  type Datastore (line 50) | type Datastore interface
  type Animal (line 55) | type Animal struct

FILE: ch09/02_advantages/04_optional_dep_with_jitdi.go
  function NewLoaderWithJIT (line 3) | func NewLoaderWithJIT(ds Datastore) *LoaderWithJIT {
  type LoaderWithJIT (line 9) | type LoaderWithJIT struct
    method Load (line 17) | func (l *LoaderWithJIT) Load(ID int) (*Animal, error) {
    method cache (line 38) | func (l *LoaderWithJIT) cache() Cache {
  type noopCache (line 47) | type noopCache struct
    method Get (line 51) | func (n *noopCache) Get(ID int) *Animal {
    method Put (line 56) | func (n *noopCache) Put(ID int, value *Animal) {

FILE: ch09/02_advantages/05_loader.go
  function NewLoader (line 7) | func NewLoader(ds Datastore, cache Cache) *MyLoader {
  type MyLoader (line 14) | type MyLoader struct
    method LoadAll (line 19) | func (l *MyLoader) LoadAll() ([]interface{}, error) {

FILE: ch09/02_advantages/06_global_variable/06_global_variable.go
  type Saver (line 6) | type Saver struct
    method Do (line 9) | func (s *Saver) Do(in *User) error {
    method validate (line 18) | func (s *Saver) validate(in *User) error {
  type UserStorage (line 23) | type UserStorage interface
  type User (line 27) | type User struct

FILE: ch09/02_advantages/07_global_variable_jit/07_global_variable_jit.go
  type Saver (line 6) | type Saver struct
    method Do (line 10) | func (s *Saver) Do(in *User) error {
    method getStorage (line 20) | func (s *Saver) getStorage() UserStorage {
    method validate (line 28) | func (s *Saver) validate(in *User) error {
  type UserStorage (line 33) | type UserStorage interface
  type User (line 37) | type User struct

FILE: ch09/02_advantages/07_global_variable_jit/07_global_variable_jit_test.go
  function TestSaver_Do (line 9) | func TestSaver_Do(t *testing.T) {
  type StubUserStorage (line 30) | type StubUserStorage struct
    method Save (line 32) | func (s *StubUserStorage) Save(_ *User) error {

FILE: ch09/02_advantages/08_car_v1.go
  type CarV1 (line 3) | type CarV1 struct
    method Drive (line 7) | func (c *CarV1) Drive() {
  type Engine (line 14) | type Engine interface

FILE: ch09/02_advantages/09_car_v2.go
  type CarV2 (line 3) | type CarV2 struct
    method Drive (line 7) | func (c *CarV2) Drive() {
    method getEngine (line 16) | func (c *CarV2) getEngine() Engine {
  function newEngine (line 24) | func newEngine() Engine {

FILE: ch09/03_applying/03_initial_dao.go
  function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
  type DAO (line 21) | type DAO struct
    method Load (line 28) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {

FILE: ch09/04_disadvantages/01_uncertain_init_state.go
  type ConnectionPool (line 10) | type ConnectionPool interface
  type Sender (line 16) | type Sender struct
    method Send (line 22) | func (l *Sender) Send(ctx context.Context, payload []byte) error {
    method getConnectionPool (line 45) | func (l *Sender) getConnectionPool() ConnectionPool {
  type myConnectionPool (line 58) | type myConnectionPool struct
    method IsReady (line 62) | func (m *myConnectionPool) IsReady() <-chan struct{} {
    method Get (line 68) | func (m *myConnectionPool) Get() net.Conn {
    method Release (line 74) | func (m *myConnectionPool) Release(_ net.Conn) {
    method init (line 78) | func (m *myConnectionPool) init() {

FILE: ch09/04_disadvantages/02_certain_init_state.go
  method SendWithoutReadyCheck (line 3) | func (l *Sender) SendWithoutReadyCheck(payload []byte) error {

FILE: ch09/04_disadvantages/03_cpool_slow_constructor.go
  function newConnectionPool (line 3) | func newConnectionPool() ConnectionPool {

FILE: ch09/04_disadvantages/04_get_pool_with_once.go
  method getConnectionPoolOnce (line 3) | func (l *Sender) getConnectionPoolOnce() ConnectionPool {

FILE: ch09/acme/internal/config/config.go
  constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 18) | type Config struct
    method Logger (line 39) | func (c *Config) Logger() logging.Logger {
    method RegistrationBasePrice (line 48) | func (c *Config) RegistrationBasePrice() float64 {
    method DataDSN (line 53) | func (c *Config) DataDSN() string {
    method ExchangeBaseURL (line 58) | func (c *Config) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 63) | func (c *Config) ExchangeAPIKey() string {
    method BindAddress (line 68) | func (c *Config) BindAddress() string {
  function init (line 73) | func init() {
  function load (line 83) | func load(filename string) error {

FILE: ch09/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch09/acme/internal/logging/logging.go
  type Logger (line 8) | type Logger interface
  type LoggerStdOut (line 19) | type LoggerStdOut struct
    method Debug (line 22) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 27) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 32) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 37) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch09/acme/internal/modules/data/dao.go
  function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
  type DAO (line 21) | type DAO struct
    method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
    method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
    method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
    method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {

FILE: ch09/acme/internal/modules/data/data.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 17) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 18) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 19) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Config (line 30) | type Config interface
  type Person (line 52) | type Person struct
  type scanner (line 66) | type scanner
  function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch09/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
  type testConfig (line 261) | type testConfig struct
    method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
    method DataDSN (line 269) | func (t *testConfig) DataDSN() string {

FILE: ch09/acme/internal/modules/data/tracker.go
  type QueryTracker (line 10) | type QueryTracker interface
  type noopTracker (line 16) | type noopTracker struct
    method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
  function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
  type LogTracker (line 31) | type LogTracker struct
    method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {

FILE: ch09/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 20) | defaultPrice = 0.0
  function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
  type Config (line 31) | type Config interface
  type Converter (line 39) | type Converter struct
    method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
    method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
    method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
    method logger (line 140) | func (c *Converter) logger() logging.Logger {
  type apiResponseFormat (line 145) | type apiResponseFormat struct

FILE: ch09/acme/internal/modules/exchange/converter_ext_bounday_test.go
  function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {

FILE: ch09/acme/internal/modules/exchange/converter_int_bounday_test.go
  function TestInternalBoundaryTest (line 13) | func TestInternalBoundaryTest(t *testing.T) {
  type happyExchangeRateService (line 33) | type happyExchangeRateService struct
    method ServeHTTP (line 36) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
  type testConfig (line 52) | type testConfig struct
    method Logger (line 58) | func (t *testConfig) Logger() logging.Logger {
    method ExchangeBaseURL (line 63) | func (t *testConfig) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 68) | func (t *testConfig) ExchangeAPIKey() string {

FILE: ch09/acme/internal/modules/get/get.go
  function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
  type Config (line 24) | type Config interface
  type Getter (line 31) | type Getter struct
    method Do (line 37) | func (g *Getter) Do(ID int) (*data.Person, error) {
    method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
  type myLoader (line 60) | type myLoader interface

FILE: ch09/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {

FILE: ch09/acme/internal/modules/get/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...

FILE: ch09/acme/internal/modules/list/list.go
  function NewLister (line 17) | func NewLister(cfg Config) *Lister {
  type Config (line 24) | type Config interface
  type Lister (line 31) | type Lister struct
    method Do (line 37) | func (l *Lister) Do() ([]*data.Person, error) {
    method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
    method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
  type myLoader (line 78) | type myLoader interface

FILE: ch09/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {

FILE: ch09/acme/internal/modules/list/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...

FILE: ch09/acme/internal/modules/register/mock_my_saver_test.go
  type mockMySaver (line 15) | type mockMySaver struct
    method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...

FILE: ch09/acme/internal/modules/register/register.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
  type Exchanger (line 45) | type Exchanger interface
  type Config (line 51) | type Config interface
  type Registerer (line 63) | type Registerer struct
    method Do (line 70) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
    method validateInput (line 95) | func (r *Registerer) validateInput(in *data.Person) error {
    method getPrice (line 115) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
    method save (line 126) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
    method getSaver (line 136) | func (r *Registerer) getSaver() mySaver {
    method logger (line 144) | func (r *Registerer) logger() logging.Logger {
  type mySaver (line 149) | type mySaver interface

FILE: ch09/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 16) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 48) | func TestRegisterer_Do_error(t *testing.T) {
  type testConfig (line 79) | type testConfig struct
    method Logger (line 82) | func (t *testConfig) Logger() logging.Logger {
    method RegistrationBasePrice (line 87) | func (t *testConfig) RegistrationBasePrice() float64 {
    method DataDSN (line 92) | func (t *testConfig) DataDSN() string {
  type stubExchanger (line 96) | type stubExchanger struct
    method Exchange (line 99) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...

FILE: ch09/acme/internal/rest/get.go
  constant defaultPersonID (line 18) | defaultPersonID = 0
  constant muxVarID (line 21) | muxVarID = "id"
  type GetModel (line 26) | type GetModel interface
  type GetConfig (line 31) | type GetConfig interface
  function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
  type GetHandler (line 47) | type GetHandler struct
    method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
  type getResponseFormat (line 117) | type getResponseFormat struct

FILE: ch09/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
  type testConfig (line 146) | type testConfig struct
    method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
    method BindAddress (line 153) | func (*testConfig) BindAddress() string {

FILE: ch09/acme/internal/rest/list.go
  type ListModel (line 13) | type ListModel interface
  function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
  type ListHandler (line 26) | type ListHandler struct
    method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
  type listResponseFormat (line 66) | type listResponseFormat struct
  type listResponseItemFormat (line 70) | type listResponseItemFormat struct

FILE: ch09/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch09/acme/internal/rest/mock_get_model_test.go
  type MockGetModel (line 13) | type MockGetModel struct
    method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {

FILE: ch09/acme/internal/rest/mock_list_model_test.go
  type MockListModel (line 13) | type MockListModel struct
    method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {

FILE: ch09/acme/internal/rest/mock_register_model_test.go
  type MockRegisterModel (line 15) | type MockRegisterModel struct
    method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...

FILE: ch09/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch09/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch09/acme/internal/rest/register.go
  type RegisterModel (line 15) | type RegisterModel interface
  function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
  type RegisterHandler (line 29) | type RegisterHandler struct
    method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
  type registerRequest (line 85) | type registerRequest struct

FILE: ch09/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {

FILE: ch09/acme/internal/rest/server.go
  type Config (line 11) | type Config interface
  function New (line 17) | func New(cfg Config,
  type Server (line 32) | type Server struct
    method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {

FILE: ch09/acme/main.go
  function main (line 14) | func main() {

FILE: ch09/fake.go
  function init (line 3) | func init() {

FILE: ch10/01_intro_to_wire/01_simple/main.go
  function main (line 10) | func main() {
  function ProvideFetcher (line 21) | func ProvideFetcher() *Fetcher {
  type Fetcher (line 26) | type Fetcher struct
    method GoFetch (line 29) | func (f *Fetcher) GoFetch() (string, error) {

FILE: ch10/01_intro_to_wire/01_simple/wire.go
  function initializeDeps (line 11) | func initializeDeps() *Fetcher {

FILE: ch10/01_intro_to_wire/01_simple/wire_gen.go
  function initializeDeps (line 10) | func initializeDeps() *Fetcher {

FILE: ch10/01_intro_to_wire/02_params/main.go
  function main (line 10) | func main() {
  function ProvideFetcher (line 21) | func ProvideFetcher(cache *Cache) *Fetcher {
  function ProvideCache (line 27) | func ProvideCache() *Cache {
  type Cache (line 31) | type Cache struct
    method Get (line 33) | func (c *Cache) Get(key string) (string, error) {
    method Set (line 37) | func (c *Cache) Set(key string, value string) error {
  type Fetcher (line 41) | type Fetcher struct
    method GoFetch (line 45) | func (f *Fetcher) GoFetch() (string, error) {

FILE: ch10/01_intro_to_wire/02_params/wire.go
  function initializeDeps (line 11) | func initializeDeps() *Fetcher {

FILE: ch10/01_intro_to_wire/02_params/wire_gen.go
  function initializeDeps (line 10) | func initializeDeps() *Fetcher {

FILE: ch10/01_intro_to_wire/03_error/main.go
  function main (line 10) | func main() {
  function ProvideFetcher (line 24) | func ProvideFetcher(cache *Cache) *Fetcher {
  function ProvideCache (line 30) | func ProvideCache() (*Cache, error) {
  type Cache (line 41) | type Cache struct
    method Start (line 43) | func (c *Cache) Start() error {
    method Get (line 47) | func (c *Cache) Get(key string) (string, error) {
    method Set (line 51) | func (c *Cache) Set(key string, value string) error {
  type Fetcher (line 55) | type Fetcher struct
    method GoFetch (line 59) | func (f *Fetcher) GoFetch() (string, error) {

FILE: ch10/01_intro_to_wire/03_error/wire.go
  function initializeDeps (line 11) | func initializeDeps() (*Fetcher, error) {

FILE: ch10/01_intro_to_wire/03_error/wire_gen.go
  function initializeDeps (line 10) | func initializeDeps() (*Fetcher, error) {

FILE: ch10/01_intro_to_wire/04_without_pset/main.go
  function main (line 11) | func main() {

FILE: ch10/01_intro_to_wire/04_without_pset/wire.go
  function initializeServer (line 20) | func initializeServer() (*rest.Server, error) {

FILE: ch10/01_intro_to_wire/04_without_pset/wire_gen.go
  function initializeServer (line 22) | func initializeServer() (*rest.Server, error) {

FILE: ch10/02_advantages/01_dig/main.go
  function main (line 19) | func main() {
  function BuildContainer (line 36) | func BuildContainer() *dig.Container {

FILE: ch10/02_advantages/02_instantiation_order/handler.go
  function NewGetPersonHandler (line 7) | func NewGetPersonHandler(model *GetPersonModel) *GetPersonHandler {
  type GetPersonHandler (line 13) | type GetPersonHandler struct
    method ServeHTTP (line 17) | func (g *GetPersonHandler) ServeHTTP(response http.ResponseWriter, req...

FILE: ch10/02_advantages/02_instantiation_order/injectors.go
  function initializeDeps (line 11) | func initializeDeps() *GetPersonHandler {

FILE: ch10/02_advantages/02_instantiation_order/main.go
  function main (line 3) | func main() {

FILE: ch10/02_advantages/02_instantiation_order/model.go
  function NewGetPersonModel (line 8) | func NewGetPersonModel(db *sql.DB) *GetPersonModel {
  type GetPersonModel (line 14) | type GetPersonModel struct
    method LoadByID (line 18) | func (g *GetPersonModel) LoadByID(ID int) (*Person, error) {
  type Person (line 22) | type Person struct

FILE: ch10/02_advantages/02_instantiation_order/providers.go
  function ProvideHandler (line 9) | func ProvideHandler(model *GetPersonModel) *GetPersonHandler {
  function ProvideModel (line 15) | func ProvideModel(db *sql.DB) *GetPersonModel {
  function ProvideDatabase (line 21) | func ProvideDatabase() *sql.DB {

FILE: ch10/02_advantages/02_instantiation_order/wire_gen.go
  function initializeDeps (line 10) | func initializeDeps() *GetPersonHandler {

FILE: ch10/03_applying/01_before_config/main.go
  function main (line 18) | func main() {

FILE: ch10/03_applying/02_after_config/main.go
  function main (line 17) | func main() {

FILE: ch10/03_applying/02_after_config/wire.go
  function initializeConfig (line 15) | func initializeConfig() (*config.Config, error) {

FILE: ch10/03_applying/02_after_config/wire_gen.go
  function initializeConfig (line 17) | func initializeConfig() (*config.Config, error) {

FILE: ch10/03_applying/03_after_exchange/main.go
  function main (line 19) | func main() {

FILE: ch10/03_applying/03_after_exchange/wire.go
  function initializeConfig (line 16) | func initializeConfig() (*config.Config, error) {
  function initializeExchanger (line 21) | func initializeExchanger() (*exchange.Converter, error) {

FILE: ch10/03_applying/04_after_model/main.go
  function main (line 19) | func main() {

FILE: ch10/03_applying/04_after_model/wire.go
  function initializeConfig (line 18) | func initializeConfig() (*config.Config, error) {
  function initializeGetter (line 23) | func initializeGetter() (*get.Getter, error) {
  function initializeLister (line 28) | func initializeLister() (*list.Lister, error) {
  function initializeRegisterer (line 33) | func initializeRegisterer() (*register.Registerer, error) {

FILE: ch10/03_applying/05_after_rest/main.go
  function main (line 19) | func main() {

FILE: ch10/03_applying/05_after_rest/wire.go
  function initializeServer (line 15) | func initializeServer() (*rest.Server, error) {

FILE: ch10/03_applying/05_after_rest/wire_gen.go
  function initializeServer (line 22) | func initializeServer() (*rest.Server, error) {

FILE: ch10/03_applying/06_build_tag.go
  function sayHello (line 9) | func sayHello() {

FILE: ch10/03_applying/06_build_tag_inverse.go
  function sayHello (line 9) | func sayHello() {

FILE: ch10/03_applying/06_main.go
  function main (line 3) | func main() {

FILE: ch10/04_disadvantages/01_complexity/main.go
  constant configFile (line 11) | configFile = "config.json"
  function main (line 14) | func main() {
  type Config (line 57) | type Config struct
  type Logger (line 61) | type Logger struct
    method Debug (line 65) | func (l *Logger) Debug(msg string, args ...interface{}) {
    method Warn (line 69) | func (l *Logger) Warn(msg string, args ...interface{}) {
    method Error (line 73) | func (l *Logger) Error(msg string, args ...interface{}) {
  type Server (line 77) | type Server struct
    method Listen (line 81) | func (s *Server) Listen() {

FILE: ch10/acme/internal/config/config.go
  constant DefaultEnvVar (line 13) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 16) | type Config struct
    method Logger (line 37) | func (c *Config) Logger() logging.Logger {
    method RegistrationBasePrice (line 46) | func (c *Config) RegistrationBasePrice() float64 {
    method DataDSN (line 51) | func (c *Config) DataDSN() string {
    method ExchangeBaseURL (line 56) | func (c *Config) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 61) | func (c *Config) ExchangeAPIKey() string {
    method BindAddress (line 66) | func (c *Config) BindAddress() string {
  function Load (line 71) | func Load() (*Config, error) {
  function load (line 88) | func load(filename string) (*Config, error) {

FILE: ch10/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch10/acme/internal/logging/logging.go
  type Logger (line 8) | type Logger interface
  type LoggerStdOut (line 19) | type LoggerStdOut struct
    method Debug (line 22) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 27) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 32) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 37) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch10/acme/internal/modules/data/dao.go
  function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
  type DAO (line 21) | type DAO struct
    method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
    method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
    method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
    method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {

FILE: ch10/acme/internal/modules/data/data.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 17) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 18) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 19) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Config (line 30) | type Config interface
  type Person (line 52) | type Person struct
  type scanner (line 66) | type scanner
  function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch10/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
  type testConfig (line 261) | type testConfig struct
    method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
    method DataDSN (line 269) | func (t *testConfig) DataDSN() string {

FILE: ch10/acme/internal/modules/data/tracker.go
  type QueryTracker (line 10) | type QueryTracker interface
  type noopTracker (line 16) | type noopTracker struct
    method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
  function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
  type LogTracker (line 31) | type LogTracker struct
    method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {

FILE: ch10/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 20) | defaultPrice = 0.0
  function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
  type Config (line 31) | type Config interface
  type Converter (line 39) | type Converter struct
    method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
    method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
    method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
    method logger (line 140) | func (c *Converter) logger() logging.Logger {
  type apiResponseFormat (line 145) | type apiResponseFormat struct

FILE: ch10/acme/internal/modules/exchange/converter_ext_bounday_test.go
  function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {

FILE: ch10/acme/internal/modules/exchange/converter_int_bounday_test.go
  function TestInternalBoundaryTest (line 13) | func TestInternalBoundaryTest(t *testing.T) {
  type happyExchangeRateService (line 33) | type happyExchangeRateService struct
    method ServeHTTP (line 36) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
  type testConfig (line 52) | type testConfig struct
    method Logger (line 58) | func (t *testConfig) Logger() logging.Logger {
    method ExchangeBaseURL (line 63) | func (t *testConfig) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 68) | func (t *testConfig) ExchangeAPIKey() string {

FILE: ch10/acme/internal/modules/get/get.go
  function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
  type Config (line 24) | type Config interface
  type Getter (line 31) | type Getter struct
    method Do (line 37) | func (g *Getter) Do(ID int) (*data.Person, error) {
    method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
  type myLoader (line 60) | type myLoader interface

FILE: ch10/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {

FILE: ch10/acme/internal/modules/get/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...

FILE: ch10/acme/internal/modules/list/list.go
  function NewLister (line 17) | func NewLister(cfg Config) *Lister {
  type Config (line 24) | type Config interface
  type Lister (line 31) | type Lister struct
    method Do (line 37) | func (l *Lister) Do() ([]*data.Person, error) {
    method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
    method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
  type myLoader (line 78) | type myLoader interface

FILE: ch10/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {

FILE: ch10/acme/internal/modules/list/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...

FILE: ch10/acme/internal/modules/register/mock_my_saver_test.go
  type mockMySaver (line 15) | type mockMySaver struct
    method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...

FILE: ch10/acme/internal/modules/register/register.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
  type Exchanger (line 45) | type Exchanger interface
  type Config (line 51) | type Config interface
  type Registerer (line 63) | type Registerer struct
    method Do (line 70) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
    method validateInput (line 95) | func (r *Registerer) validateInput(in *data.Person) error {
    method getPrice (line 115) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
    method save (line 126) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
    method getSaver (line 136) | func (r *Registerer) getSaver() mySaver {
    method logger (line 144) | func (r *Registerer) logger() logging.Logger {
  type mySaver (line 149) | type mySaver interface

FILE: ch10/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 16) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 48) | func TestRegisterer_Do_error(t *testing.T) {
  type testConfig (line 79) | type testConfig struct
    method Logger (line 82) | func (t *testConfig) Logger() logging.Logger {
    method RegistrationBasePrice (line 87) | func (t *testConfig) RegistrationBasePrice() float64 {
    method DataDSN (line 92) | func (t *testConfig) DataDSN() string {
  type stubExchanger (line 96) | type stubExchanger struct
    method Exchange (line 99) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...

FILE: ch10/acme/internal/rest/get.go
  constant defaultPersonID (line 18) | defaultPersonID = 0
  constant muxVarID (line 21) | muxVarID = "id"
  type GetModel (line 26) | type GetModel interface
  type GetConfig (line 31) | type GetConfig interface
  function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
  type GetHandler (line 47) | type GetHandler struct
    method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
  type getResponseFormat (line 117) | type getResponseFormat struct

FILE: ch10/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
  type testConfig (line 146) | type testConfig struct
    method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
    method BindAddress (line 153) | func (*testConfig) BindAddress() string {

FILE: ch10/acme/internal/rest/list.go
  type ListModel (line 13) | type ListModel interface
  function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
  type ListHandler (line 26) | type ListHandler struct
    method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
  type listResponseFormat (line 66) | type listResponseFormat struct
  type listResponseItemFormat (line 70) | type listResponseItemFormat struct

FILE: ch10/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch10/acme/internal/rest/mock_get_model_test.go
  type MockGetModel (line 13) | type MockGetModel struct
    method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {

FILE: ch10/acme/internal/rest/mock_list_model_test.go
  type MockListModel (line 13) | type MockListModel struct
    method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {

FILE: ch10/acme/internal/rest/mock_register_model_test.go
  type MockRegisterModel (line 15) | type MockRegisterModel struct
    method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...

FILE: ch10/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch10/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch10/acme/internal/rest/register.go
  type RegisterModel (line 15) | type RegisterModel interface
  function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
  type RegisterHandler (line 29) | type RegisterHandler struct
    method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
  type registerRequest (line 85) | type registerRequest struct

FILE: ch10/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {

FILE: ch10/acme/internal/rest/server.go
  type Config (line 11) | type Config interface
  function New (line 17) | func New(cfg Config,
  type Server (line 32) | type Server struct
    method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {

FILE: ch10/acme/main.go
  function main (line 16) | func main() {

FILE: ch10/acme/main_test.go
  function TestRegister (line 18) | func TestRegister(t *testing.T) {
  function TestGet (line 46) | func TestGet(t *testing.T) {
  function TestList (line 65) | func TestList(t *testing.T) {
  function startTestServer (line 84) | func startTestServer(t *testing.T, ctx context.Context) string {
  function getFreePort (line 110) | func getFreePort() (string, error) {
  function getPort (line 139) | func getPort(addr fmt.Stringer) (string, error) {

FILE: ch10/acme/wire.go
  function initializeServer (line 16) | func initializeServer() (*rest.Server, error) {
  function initializeServerCustomConfig (line 21) | func initializeServerCustomConfig(_ exchange.Config, _ get.Config, _ lis...

FILE: ch10/acme/wire_gen.go
  function initializeServer (line 19) | func initializeServer() (*rest.Server, error) {
  function initializeServerCustomConfig (line 32) | func initializeServerCustomConfig(exchangeConfig exchange.Config, getCon...

FILE: ch10/fake.go
  function init (line 3) | func init() {

FILE: ch11/01_di_induced_damage/01_long_param/01_long_param.go
  function NewMyHandler (line 8) | func NewMyHandler(logger Logger, stats Instrumentation,
  type MyHandler (line 19) | type MyHandler struct
    method ServeHTTP (line 23) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
  type Logger (line 28) | type Logger interface
  type Instrumentation (line 36) | type Instrumentation interface
  type Parser (line 42) | type Parser interface
  type Formatter (line 47) | type Formatter interface
  type RateLimiter (line 52) | type RateLimiter interface
  type Datastore (line 58) | type Datastore interface
  type Cache (line 63) | type Cache interface

FILE: ch11/01_di_induced_damage/02_long_param/01_long_param.go
  function NewMyHandler (line 8) | func NewMyHandler(logger Logger, stats Instrumentation,
  type MyHandler (line 19) | type MyHandler struct
    method ServeHTTP (line 23) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
  type Logger (line 28) | type Logger interface
  type Instrumentation (line 36) | type Instrumentation interface
  type Parser (line 42) | type Parser interface
  type Formatter (line 47) | type Formatter interface
  type RateLimiter (line 52) | type RateLimiter interface
  type Loader (line 58) | type Loader interface

FILE: ch11/01_di_induced_damage/03_long_param/01_long_param.go
  function NewMyHandler (line 8) | func NewMyHandler(config Config,
  type MyHandler (line 23) | type MyHandler struct
    method ServeHTTP (line 31) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
  type Config (line 52) | type Config interface
  type Logger (line 58) | type Logger interface
  type Instrumentation (line 66) | type Instrumentation interface
  type Parser (line 72) | type Parser interface
  type Formatter (line 77) | type Formatter interface
  type RateLimiter (line 82) | type RateLimiter interface
  type Loader (line 88) | type Loader interface

FILE: ch11/01_di_induced_damage/04_long_param/01_long_param.go
  function NewFancyFormatHandler (line 8) | func NewFancyFormatHandler(config Config,
  type FancyFormatHandler (line 25) | type FancyFormatHandler struct
  type MyHandler (line 30) | type MyHandler struct
    method ServeHTTP (line 38) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
  type Config (line 59) | type Config interface
  type Logger (line 65) | type Logger interface
  type Instrumentation (line 73) | type Instrumentation interface
  type Parser (line 79) | type Parser interface
  type Formatter (line 84) | type Formatter interface
  type FancyFormatter (line 89) | type FancyFormatter struct
    method Format (line 91) | func (f *FancyFormatter) Format(response http.ResponseWriter, data []b...
  type RateLimiter (line 98) | type RateLimiter interface
  type Loader (line 104) | type Loader interface

FILE: ch11/01_di_induced_damage/04_long_param/01_long_param_test.go
  function TestNewFancyFormatHandler (line 12) | func TestNewFancyFormatHandler(t *testing.T) {
  type stubConfig (line 33) | type stubConfig struct
    method Logger (line 35) | func (s *stubConfig) Logger() Logger {
    method Instrumentation (line 39) | func (s *stubConfig) Instrumentation() Instrumentation {
  type stubLogger (line 43) | type stubLogger struct
    method Error (line 45) | func (s *stubLogger) Error(message string, args ...interface{}) {
    method Warn (line 49) | func (s *stubLogger) Warn(message string, args ...interface{}) {
    method Info (line 53) | func (s *stubLogger) Info(message string, args ...interface{}) {
    method Debug (line 57) | func (s *stubLogger) Debug(message string, args ...interface{}) {
  type stubInstrumentation (line 61) | type stubInstrumentation struct
    method Count (line 63) | func (s *stubInstrumentation) Count(key string, value int) {
    method Duration (line 67) | func (s *stubInstrumentation) Duration(key string, start time.Time) {
  type stubParser (line 71) | type stubParser struct
    method Extract (line 73) | func (s *stubParser) Extract(req *http.Request) (int, error) {
  type stubRateLimiter (line 77) | type stubRateLimiter struct
    method Acquire (line 79) | func (s *stubRateLimiter) Acquire() {
    method Release (line 83) | func (s *stubRateLimiter) Release() {
  type stubLoader (line 87) | type stubLoader struct
    method Load (line 89) | func (s *stubLoader) Load(ID int) ([]byte, error) {

FILE: ch11/01_di_induced_damage/05_inject_sql/01_interface.go
  type Connection (line 8) | type Connection interface

FILE: ch11/01_di_induced_damage/06_inject_sql/01_interface.go
  type Database (line 7) | type Database interface
  type Row (line 13) | type Row interface
  type Rows (line 17) | type Rows interface
  type Result (line 23) | type Result interface

FILE: ch11/01_di_induced_damage/06_inject_sql/02_implementation.go
  type DatabaseImpl (line 8) | type DatabaseImpl struct
    method QueryRowContext (line 12) | func (c *DatabaseImpl) QueryRowContext(ctx context.Context, query stri...
    method QueryContext (line 16) | func (c *DatabaseImpl) QueryContext(ctx context.Context, query string,...
    method ExecContext (line 20) | func (c *DatabaseImpl) ExecContext(ctx context.Context, query string, ...
  type RowImpl (line 24) | type RowImpl struct
    method Scan (line 28) | func (r *RowImpl) Scan(dest ...interface{}) error {
  type RowsImpl (line 32) | type RowsImpl struct
    method Scan (line 36) | func (r RowsImpl) Scan(dest ...interface{}) error {
    method Close (line 40) | func (r RowsImpl) Close() error {
    method Next (line 44) | func (r RowsImpl) Next() bool {
  type ResultImpl (line 48) | type ResultImpl struct
    method LastInsertId (line 52) | func (r *ResultImpl) LastInsertId() (int64, error) {
    method RowsAffected (line 56) | func (r *ResultImpl) RowsAffected() (int64, error) {

FILE: ch11/01_di_induced_damage/06_inject_sql/02_implementation_test.go
  function TestImplements (line 9) | func TestImplements(t *testing.T) {

FILE: ch11/01_di_induced_damage/06_inject_sql/dao.go
  function NewDAO (line 13) | func NewDAO(cfg Config) *DAO {
  type DAO (line 23) | type DAO struct
    method Load (line 30) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
    method LoadAll (line 61) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
    method Save (line 104) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {

FILE: ch11/01_di_induced_damage/06_inject_sql/data.go
  constant defaultPersonID (line 12) | defaultPersonID = 0
  constant sqlAllColumns (line 15) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 16) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 17) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 18) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Config (line 29) | type Config interface
  type Person (line 48) | type Person struct
  type scanner (line 62) | type scanner
  function populatePerson (line 65) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch11/01_di_induced_damage/06_inject_sql/data_test.go
  function TestSave_happyPath (line 15) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 51) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 88) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 120) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 188) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 255) | func convertSQLToRegex(in string) string {
  type testConfig (line 259) | type testConfig struct
    method DataDSN (line 262) | func (t *testConfig) DataDSN() string {

FILE: ch11/01_di_induced_damage/07_needless_indirection/example_test.go
  function TestExample (line 12) | func TestExample(t *testing.T) {

FILE: ch11/01_di_induced_damage/08_needless_indirection/01_mux.go
  type MyMux (line 8) | type MyMux interface
  function buildRouter (line 15) | func buildRouter(mux MyMux) {
  type getEndpoint (line 21) | type getEndpoint struct
    method ServeHTTP (line 23) | func (*getEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
  type listEndpoint (line 27) | type listEndpoint struct
    method ServeHTTP (line 29) | func (*listEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
  type saveEndpoint (line 33) | type saveEndpoint struct
    method ServeHTTP (line 35) | func (*saveEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {

FILE: ch11/01_di_induced_damage/08_needless_indirection/01_mux_test.go
  function TestBuildRouter (line 9) | func TestBuildRouter(t *testing.T) {

FILE: ch11/01_di_induced_damage/08_needless_indirection/mock_my_mux_test.go
  type MockMyMux (line 14) | type MockMyMux struct
    method Handle (line 19) | func (_m *MockMyMux) Handle(pattern string, handler http.Handler) {
    method Handler (line 24) | func (_m *MockMyMux) Handler(req *http.Request) (http.Handler, string) {
    method ServeHTTP (line 47) | func (_m *MockMyMux) ServeHTTP(resp http.ResponseWriter, req *http.Req...

FILE: ch11/01_di_induced_damage/09_needless_indirection/01_mux.go
  function buildRouter (line 8) | func buildRouter(mux *http.ServeMux) {
  type getEndpoint (line 14) | type getEndpoint struct
    method ServeHTTP (line 16) | func (*getEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
  type listEndpoint (line 20) | type listEndpoint struct
    method ServeHTTP (line 22) | func (*listEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
  type saveEndpoint (line 26) | type saveEndpoint struct
    method ServeHTTP (line 28) | func (*saveEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {

FILE: ch11/01_di_induced_damage/09_needless_indirection/01_mux_test.go
  function TestBuildRouter (line 10) | func TestBuildRouter(t *testing.T) {
  function extractHandler (line 22) | func extractHandler(router *http.ServeMux, path string) http.Handler {

FILE: ch11/01_di_induced_damage/10_needless_indirection/01_mux_e2e.go
  function buildRouter (line 8) | func buildRouter(mux *http.ServeMux) {
  type getEndpoint (line 14) | type getEndpoint struct
    method ServeHTTP (line 16) | func (*getEndpoint) ServeHTTP(resp http.ResponseWriter, _ *http.Reques...
  type listEndpoint (line 20) | type listEndpoint struct
    method ServeHTTP (line 22) | func (*listEndpoint) ServeHTTP(resp http.ResponseWriter, _ *http.Reque...
  type saveEndpoint (line 26) | type saveEndpoint struct
    method ServeHTTP (line 28) | func (*saveEndpoint) ServeHTTP(resp http.ResponseWriter, _ *http.Reque...

FILE: ch11/01_di_induced_damage/10_needless_indirection/01_mux_e2e_test.go
  function TestBuildRouter (line 12) | func TestBuildRouter(t *testing.T) {
  function doGet (line 36) | func doGet(t *testing.T, address string) string {

FILE: ch11/01_di_induced_damage/11_service_locator/01_service_locator.go
  function NewServiceLocator (line 3) | func NewServiceLocator() *ServiceLocator {
  type ServiceLocator (line 9) | type ServiceLocator struct
    method Store (line 14) | func (s *ServiceLocator) Store(key string, dep interface{}) {
    method Get (line 19) | func (s *ServiceLocator) Get(key string) interface{} {

FILE: ch11/01_di_induced_damage/11_service_locator/02_usage.go
  function Example (line 3) | func Example() {
  function buildServiceLocator (line 8) | func buildServiceLocator() *ServiceLocator {
  function useServiceLocator (line 19) | func useServiceLocator(locator *ServiceLocator) {
  function useServiceLocatorExtended (line 27) | func useServiceLocatorExtended(locator *ServiceLocator) {
  type Logger (line 42) | type Logger interface
  type myLogger (line 46) | type myLogger struct
    method Info (line 48) | func (m *myLogger) Info(message string, args ...interface{}) {
  type Converter (line 52) | type Converter interface
  type myConverter (line 56) | type myConverter struct
    method Convert (line 58) | func (m *myConverter) Convert(in float64) (float64, error) {

FILE: ch11/02_premature_future/get.go
  constant defaultPersonID (line 15) | defaultPersonID = 0
  constant muxVarID (line 18) | muxVarID = "id"
  type GetModel (line 22) | type GetModel interface
  type GetConfig (line 27) | type GetConfig interface
  type Formatter (line 32) | type Formatter interface
  function NewGetHandler (line 37) | func NewGetHandler(cfg GetConfig, model GetModel, formatter Formatter) *...
  type GetHandler (line 46) | type GetHandler struct
    method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method buildOutput (line 103) | func (h *GetHandler) buildOutput(writer io.Writer, person *Person) err...
  type getResponseFormat (line 124) | type getResponseFormat struct
  type Person (line 132) | type Person struct
  type Logger (line 140) | type Logger interface

FILE: ch11/03_mocking_http_requests/converter.go
  constant urlFormat (line 15) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 18) | defaultPrice = 0.0
  type Config (line 22) | type Config interface
  function NewConverter (line 29) | func NewConverter(cfg Config, requester Requester) *Converter {
  type Converter (line 37) | type Converter struct
    method Exchange (line 43) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
    method loadRateFromServer (line 61) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
    method extractRate (line 84) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 107) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
    method logger (line 125) | func (c *Converter) logger() Logger {
  type apiResponseFormat (line 130) | type apiResponseFormat struct
  type Requester (line 136) | type Requester interface
  type Requesterer (line 141) | type Requesterer struct
    method doRequest (line 144) | func (r *Requesterer) doRequest(ctx context.Context, url string) (*htt...
  type Logger (line 161) | type Logger interface
  type stubLogger (line 166) | type stubLogger struct
    method Warn (line 168) | func (l *stubLogger) Warn(message string, args ...interface{}) {
    method Error (line 172) | func (l *stubLogger) Error(message string, args ...interface{}) {

FILE: ch11/03_mocking_http_requests/converter_test.go
  function TestExchange_invalidResponse (line 14) | func TestExchange_invalidResponse(t *testing.T) {
  type testConfig (line 45) | type testConfig struct
    method Logger (line 48) | func (t *testConfig) Logger() Logger {
    method ExchangeBaseURL (line 52) | func (t *testConfig) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 56) | func (t *testConfig) ExchangeAPIKey() string {

FILE: ch11/03_mocking_http_requests/mock_requester_test.go
  type mockRequester (line 15) | type mockRequester struct
    method doRequest (line 20) | func (_m *mockRequester) doRequest(ctx context.Context, url string) (*...

FILE: ch11/acme/internal/config/config.go
  constant DefaultEnvVar (line 13) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 16) | type Config struct
    method Logger (line 37) | func (c *Config) Logger() logging.Logger {
    method RegistrationBasePrice (line 46) | func (c *Config) RegistrationBasePrice() float64 {
    method DataDSN (line 51) | func (c *Config) DataDSN() string {
    method ExchangeBaseURL (line 56) | func (c *Config) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 61) | func (c *Config) ExchangeAPIKey() string {
    method BindAddress (line 66) | func (c *Config) BindAddress() string {
  function Load (line 71) | func Load() (*Config, error) {
  function load (line 88) | func load(filename string) (*Config, error) {

FILE: ch11/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch11/acme/internal/logging/logging.go
  type Logger (line 8) | type Logger interface
  type LoggerStdOut (line 16) | type LoggerStdOut struct
    method Debug (line 19) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 24) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 29) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 34) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch11/acme/internal/modules/data/dao.go
  function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
  type DAO (line 21) | type DAO struct
    method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
    method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
    method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
    method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {

FILE: ch11/acme/internal/modules/data/data.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 17) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 18) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 19) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Config (line 30) | type Config interface
  type Person (line 52) | type Person struct
  type scanner (line 66) | type scanner
  function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch11/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
  type testConfig (line 261) | type testConfig struct
    method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
    method DataDSN (line 269) | func (t *testConfig) DataDSN() string {

FILE: ch11/acme/internal/modules/data/tracker.go
  type QueryTracker (line 10) | type QueryTracker interface
  type noopTracker (line 16) | type noopTracker struct
    method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
  function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
  type LogTracker (line 31) | type LogTracker struct
    method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {

FILE: ch11/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 20) | defaultPrice = 0.0
  function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
  type Config (line 31) | type Config interface
  type Converter (line 39) | type Converter struct
    method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
    method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
    method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
    method logger (line 140) | func (c *Converter) logger() logging.Logger {
  type apiResponseFormat (line 145) | type apiResponseFormat struct

FILE: ch11/acme/internal/modules/exchange/converter_ext_bounday_test.go
  function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {

FILE: ch11/acme/internal/modules/exchange/converter_int_bounday_test.go
  function TestInternalBoundaryTest (line 14) | func TestInternalBoundaryTest(t *testing.T) {
  type happyExchangeRateService (line 34) | type happyExchangeRateService struct
    method ServeHTTP (line 37) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
  function TestExchange_invalidResponseFromServer (line 52) | func TestExchange_invalidResponseFromServer(t *testing.T) {
  type testConfig (line 81) | type testConfig struct
    method Logger (line 87) | func (t *testConfig) Logger() logging.Logger {
    method ExchangeBaseURL (line 92) | func (t *testConfig) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 97) | func (t *testConfig) ExchangeAPIKey() string {

FILE: ch11/acme/internal/modules/get/get.go
  function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
  type Config (line 24) | type Config interface
  type Getter (line 31) | type Getter struct
    method Do (line 37) | func (g *Getter) Do(ID int) (*Person, error) {
    method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
    method convert (line 59) | func (g *Getter) convert(in *data.Person) *Person {
  type myLoader (line 70) | type myLoader interface
  type Person (line 76) | type Person struct

FILE: ch11/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {

FILE: ch11/acme/internal/modules/get/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...

FILE: ch11/acme/internal/modules/list/list.go
  function NewLister (line 17) | func NewLister(cfg Config) *Lister {
  type Config (line 24) | type Config interface
  type Lister (line 31) | type Lister struct
    method Do (line 37) | func (l *Lister) Do() ([]*Person, error) {
    method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
    method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
    method convert (line 77) | func (l *Lister) convert(in []*data.Person) []*Person {
  type myLoader (line 92) | type myLoader interface
  type Person (line 98) | type Person struct

FILE: ch11/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {

FILE: ch11/acme/internal/modules/list/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...

FILE: ch11/acme/internal/modules/register/mock_exchanger_test.go
  type MockExchanger (line 14) | type MockExchanger struct
    method Exchange (line 19) | func (_m *MockExchanger) Exchange(ctx context.Context, basePrice float...

FILE: ch11/acme/internal/modules/register/mock_my_saver_test.go
  type mockMySaver (line 15) | type mockMySaver struct
    method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...

FILE: ch11/acme/internal/modules/register/register.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
  type Exchanger (line 46) | type Exchanger interface
  type Config (line 52) | type Config interface
  type Registerer (line 64) | type Registerer struct
    method Do (line 71) | func (r *Registerer) Do(ctx context.Context, in *Person) (int, error) {
    method validateInput (line 96) | func (r *Registerer) validateInput(in *Person) error {
    method getPrice (line 116) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
    method save (line 127) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
    method getSaver (line 137) | func (r *Registerer) getSaver() mySaver {
    method logger (line 145) | func (r *Registerer) logger() logging.Logger {
    method convert (line 149) | func (r *Registerer) convert(in *Person) *data.Person {
  type mySaver (line 160) | type mySaver interface
  type Person (line 166) | type Person struct

FILE: ch11/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 15) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 47) | func TestRegisterer_Do_error(t *testing.T) {
  function TestRegisterer_Do_exchangeError (line 77) | func TestRegisterer_Do_exchangeError(t *testing.T) {
  type testConfig (line 113) | type testConfig struct
    method Logger (line 116) | func (t *testConfig) Logger() logging.Logger {
    method RegistrationBasePrice (line 121) | func (t *testConfig) RegistrationBasePrice() float64 {
    method DataDSN (line 126) | func (t *testConfig) DataDSN() string {
  type stubExchanger (line 130) | type stubExchanger struct
    method Exchange (line 133) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...

FILE: ch11/acme/internal/rest/get.go
  constant defaultPersonID (line 18) | defaultPersonID = 0
  constant muxVarID (line 21) | muxVarID = "id"
  type GetModel (line 26) | type GetModel interface
  type GetConfig (line 31) | type GetConfig interface
  function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
  type GetHandler (line 47) | type GetHandler struct
    method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *get.Person) e...
  type getResponseFormat (line 117) | type getResponseFormat struct

FILE: ch11/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
  type testConfig (line 146) | type testConfig struct
    method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
    method BindAddress (line 153) | func (*testConfig) BindAddress() string {

FILE: ch11/acme/internal/rest/list.go
  type ListModel (line 13) | type ListModel interface
  function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
  type ListHandler (line 26) | type ListHandler struct
    method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*list.Perso...
  type listResponseFormat (line 66) | type listResponseFormat struct
  type listResponseItemFormat (line 70) | type listResponseItemFormat struct

FILE: ch11/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch11/acme/internal/rest/mock_get_model_test.go
  type MockGetModel (line 13) | type MockGetModel struct
    method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*get.Person, error) {

FILE: ch11/acme/internal/rest/mock_list_model_test.go
  type MockListModel (line 13) | type MockListModel struct
    method Do (line 18) | func (_m *MockListModel) Do() ([]*list.Person, error) {

FILE: ch11/acme/internal/rest/mock_register_model_test.go
  type MockRegisterModel (line 15) | type MockRegisterModel struct
    method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *register.Pers...

FILE: ch11/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch11/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch11/acme/internal/rest/register.go
  type RegisterModel (line 15) | type RegisterModel interface
  function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
  type RegisterHandler (line 29) | type RegisterHandler struct
    method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
  type registerRequest (line 85) | type registerRequest struct

FILE: ch11/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {

FILE: ch11/acme/internal/rest/server.go
  type Config (line 11) | type Config interface
  function New (line 17) | func New(cfg Config,
  type Server (line 32) | type Server struct
    method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {

FILE: ch11/acme/main.go
  function main (line 16) | func main() {

FILE: ch11/acme/main_test.go
  function TestRegister (line 18) | func TestRegister(t *testing.T) {
  function TestGet (line 46) | func TestGet(t *testing.T) {
  function TestList (line 65) | func TestList(t *testing.T) {
  function startTestServer (line 84) | func startTestServer(t *testing.T, ctx context.Context) string {
  function getFreePort (line 110) | func getFreePort() (string, error) {
  function getPort (line 139) | func getPort(addr fmt.Stringer) (string, error) {

FILE: ch11/acme/wire.go
  function initializeServer (line 16) | func initializeServer() (*rest.Server, error) {
  function initializeServerCustomConfig (line 21) | func initializeServerCustomConfig(_ exchange.Config, _ get.Config, _ lis...

FILE: ch11/acme/wire_gen.go
  function initializeServer (line 19) | func initializeServer() (*rest.Server, error) {
  function initializeServerCustomConfig (line 32) | func initializeServerCustomConfig(exchangeConfig exchange.Config, getCon...

FILE: ch12/01_improvements/01_test_logging_test.go
  function TestLogging (line 11) | func TestLogging(t *testing.T) {
  type Calculator (line 27) | type Calculator struct
    method divide (line 31) | func (c *Calculator) divide(dividend int, divisor int) int {
  type Logger (line 41) | type Logger interface
  type LogRecorder (line 46) | type LogRecorder struct
    method Error (line 50) | func (l *LogRecorder) Error(message string, args ...interface{}) {

FILE: ch12/03_testing/01_mock_get_model.go
  type MockGetModel (line 7) | type MockGetModel struct
    method Do (line 11) | func (_m *MockGetModel) Do(ID int) (*Person, error) {
  type Person (line 21) | type Person struct

FILE: ch12/04_new_service/01_data_with_cache/dao.go
  type DAO (line 13) | type DAO struct
    method Load (line 23) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
    method loadFromCache (line 51) | func (d *DAO) loadFromCache(ID int) *Person {
    method saveToCache (line 71) | func (d *DAO) saveToCache(ID int, person *Person) {
    method buildCacheKey (line 84) | func (d *DAO) buildCacheKey(ID int) string {

FILE: ch12/04_new_service/01_data_with_cache/data.go
  constant sqlAllColumns (line 12) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlLoadByID (line 13) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Config (line 22) | type Config interface
  type Person (line 31) | type Person struct
  type scanner (line 45) | type scanner
  function populatePerson (line 48) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch12/04_new_service/01_data_with_cache/internal/cache/cache.go
  type Cache (line 7) | type Cache struct
    method Get (line 9) | func (c *Cache) Get(key string) ([]byte, error) {
    method Set (line 13) | func (c *Cache) Set(key string, data []byte) error {

FILE: ch12/04_new_service/01_data_with_cache/internal/logging/logging.go
  type Logger (line 8) | type Logger interface
  type LoggerStdOut (line 16) | type LoggerStdOut struct
    method Debug (line 19) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 24) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 29) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 34) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch12/acme/internal/config/config.go
  constant DefaultEnvVar (line 13) | DefaultEnvVar = "ACME_CONFIG"
  type Config (line 16) | type Config struct
    method Logger (line 37) | func (c *Config) Logger() logging.Logger {
    method RegistrationBasePrice (line 46) | func (c *Config) RegistrationBasePrice() float64 {
    method DataDSN (line 51) | func (c *Config) DataDSN() string {
    method ExchangeBaseURL (line 56) | func (c *Config) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 61) | func (c *Config) ExchangeAPIKey() string {
    method BindAddress (line 66) | func (c *Config) BindAddress() string {
  function Load (line 71) | func Load() (*Config, error) {
  function load (line 88) | func load(filename string) (*Config, error) {

FILE: ch12/acme/internal/config/config_test.go
  function TestLoad (line 10) | func TestLoad(t *testing.T) {

FILE: ch12/acme/internal/logging/logging.go
  type Logger (line 8) | type Logger interface
  type LoggerStdOut (line 16) | type LoggerStdOut struct
    method Debug (line 19) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
    method Info (line 24) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
    method Warn (line 29) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
    method Error (line 34) | func (l LoggerStdOut) Error(message string, args ...interface{}) {

FILE: ch12/acme/internal/modules/data/dao.go
  function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
  type DAO (line 21) | type DAO struct
    method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
    method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
    method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
    method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {

FILE: ch12/acme/internal/modules/data/data.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
  constant sqlInsert (line 17) | sqlInsert     = "INSERT INTO person (fullname, phone, currency, price) V...
  constant sqlLoadAll (line 18) | sqlLoadAll    = "SELECT " + sqlAllColumns + " FROM person"
  constant sqlLoadByID (line 19) | sqlLoadByID   = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
  type Config (line 30) | type Config interface
  type Person (line 52) | type Person struct
  type scanner (line 66) | type scanner
  function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {

FILE: ch12/acme/internal/modules/data/data_test.go
  function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
  function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
  function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
  function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
  function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
  function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
  type testConfig (line 261) | type testConfig struct
    method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
    method DataDSN (line 269) | func (t *testConfig) DataDSN() string {

FILE: ch12/acme/internal/modules/data/tracker.go
  type QueryTracker (line 10) | type QueryTracker interface
  type noopTracker (line 16) | type noopTracker struct
    method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
  function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
  type LogTracker (line 31) | type LogTracker struct
    method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {

FILE: ch12/acme/internal/modules/exchange/converter.go
  constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20&currencies=%s"
  constant defaultPrice (line 20) | defaultPrice = 0.0
  function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
  type Config (line 31) | type Config interface
  type Converter (line 39) | type Converter struct
    method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
    method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
    method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
    method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
    method logger (line 140) | func (c *Converter) logger() logging.Logger {
  type apiResponseFormat (line 145) | type apiResponseFormat struct

FILE: ch12/acme/internal/modules/exchange/converter_ext_bounday_test.go
  function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {

FILE: ch12/acme/internal/modules/exchange/converter_int_bounday_test.go
  function TestInternalBoundaryTest (line 14) | func TestInternalBoundaryTest(t *testing.T) {
  type happyExchangeRateService (line 34) | type happyExchangeRateService struct
    method ServeHTTP (line 37) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
  function TestExchange_invalidResponseFromServer (line 52) | func TestExchange_invalidResponseFromServer(t *testing.T) {
  type testConfig (line 82) | type testConfig struct
    method Logger (line 88) | func (t *testConfig) Logger() logging.Logger {
    method ExchangeBaseURL (line 93) | func (t *testConfig) ExchangeBaseURL() string {
    method ExchangeAPIKey (line 98) | func (t *testConfig) ExchangeAPIKey() string {

FILE: ch12/acme/internal/modules/get/get.go
  function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
  type Config (line 24) | type Config interface
  type Getter (line 31) | type Getter struct
    method Do (line 37) | func (g *Getter) Do(ID int) (*Person, error) {
    method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
    method convert (line 59) | func (g *Getter) convert(in *data.Person) *Person {
  type myLoader (line 70) | type myLoader interface
  type Person (line 76) | type Person struct

FILE: ch12/acme/internal/modules/get/go_test.go
  function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
  function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
  function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {

FILE: ch12/acme/internal/modules/get/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...

FILE: ch12/acme/internal/modules/list/list.go
  function NewLister (line 17) | func NewLister(cfg Config) *Lister {
  type Config (line 24) | type Config interface
  type Lister (line 31) | type Lister struct
    method Do (line 37) | func (l *Lister) Do() ([]*Person, error) {
    method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
    method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
    method convert (line 77) | func (l *Lister) convert(in []*data.Person) []*Person {
  type myLoader (line 92) | type myLoader interface
  type Person (line 98) | type Person struct

FILE: ch12/acme/internal/modules/list/list_test.go
  function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
  function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
  function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {

FILE: ch12/acme/internal/modules/list/mock_my_loader_test.go
  type mockMyLoader (line 15) | type mockMyLoader struct
    method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...

FILE: ch12/acme/internal/modules/register/mock_exchanger_test.go
  type MockExchanger (line 14) | type MockExchanger struct
    method Exchange (line 19) | func (_m *MockExchanger) Exchange(ctx context.Context, basePrice float...

FILE: ch12/acme/internal/modules/register/mock_my_saver_test.go
  type mockMySaver (line 15) | type mockMySaver struct
    method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...

FILE: ch12/acme/internal/modules/register/register.go
  constant defaultPersonID (line 13) | defaultPersonID = 0
  function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
  type Exchanger (line 46) | type Exchanger interface
  type Config (line 52) | type Config interface
  type Registerer (line 64) | type Registerer struct
    method Do (line 71) | func (r *Registerer) Do(ctx context.Context, in *Person) (int, error) {
    method validateInput (line 96) | func (r *Registerer) validateInput(in *Person) error {
    method getPrice (line 116) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
    method save (line 127) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
    method getSaver (line 137) | func (r *Registerer) getSaver() mySaver {
    method logger (line 145) | func (r *Registerer) logger() logging.Logger {
    method convert (line 149) | func (r *Registerer) convert(in *Person) *data.Person {
  type mySaver (line 160) | type mySaver interface
  type Person (line 166) | type Person struct

FILE: ch12/acme/internal/modules/register/register_test.go
  function TestRegisterer_Do_happyPath (line 15) | func TestRegisterer_Do_happyPath(t *testing.T) {
  function TestRegisterer_Do_error (line 47) | func TestRegisterer_Do_error(t *testing.T) {
  function TestRegisterer_Do_exchangeError (line 77) | func TestRegisterer_Do_exchangeError(t *testing.T) {
  type testConfig (line 113) | type testConfig struct
    method Logger (line 116) | func (t *testConfig) Logger() logging.Logger {
    method RegistrationBasePrice (line 121) | func (t *testConfig) RegistrationBasePrice() float64 {
    method DataDSN (line 126) | func (t *testConfig) DataDSN() string {
  type stubExchanger (line 130) | type stubExchanger struct
    method Exchange (line 133) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...

FILE: ch12/acme/internal/rest/get.go
  constant defaultPersonID (line 18) | defaultPersonID = 0
  constant muxVarID (line 21) | muxVarID = "id"
  type GetModel (line 26) | type GetModel interface
  type GetConfig (line 31) | type GetConfig interface
  function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
  type GetHandler (line 47) | type GetHandler struct
    method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
    method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
    method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *get.Person) e...
  type getResponseFormat (line 117) | type getResponseFormat struct

FILE: ch12/acme/internal/rest/get_test.go
  function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
  type testConfig (line 146) | type testConfig struct
    method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
    method BindAddress (line 153) | func (*testConfig) BindAddress() string {

FILE: ch12/acme/internal/rest/list.go
  type ListModel (line 13) | type ListModel interface
  function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
  type ListHandler (line 26) | type ListHandler struct
    method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
    method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*list.Perso...
  type listResponseFormat (line 66) | type listResponseFormat struct
  type listResponseItemFormat (line 70) | type listResponseItemFormat struct

FILE: ch12/acme/internal/rest/list_test.go
  function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {

FILE: ch12/acme/internal/rest/mock_get_model_test.go
  type MockGetModel (line 13) | type MockGetModel struct
    method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*get.Person, error) {

FILE: ch12/acme/internal/rest/mock_list_model_test.go
  type MockListModel (line 13) | type MockListModel struct
    method Do (line 18) | func (_m *MockListModel) Do() ([]*list.Person, error) {

FILE: ch12/acme/internal/rest/mock_register_model_test.go
  type MockRegisterModel (line 15) | type MockRegisterModel struct
    method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *register.Pers...

FILE: ch12/acme/internal/rest/not_found.go
  function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {

FILE: ch12/acme/internal/rest/not_found_test.go
  function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {

FILE: ch12/acme/internal/rest/register.go
  type RegisterModel (line 15) | type RegisterModel interface
  function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
  type RegisterHandler (line 29) | type RegisterHandler struct
    method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
    method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
    method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
  type registerRequest (line 85) | type registerRequest struct

FILE: ch12/acme/internal/rest/register_test.go
  function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
  function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {

FILE: ch12/acme/internal/rest/server.go
  type Config (line 11) | type Config interface
  function New (line 17) | func New(cfg Config,
  type Server (line 32) | type Server struct
    method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
    method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {

FILE: ch12/acme/main.go
  function main (line 16) | func main() {

FILE: ch12/acme/main_test.go
  function TestRegister (line 18) | func TestRegister(t *testing.T) {
  function TestGet (line 46) | func TestGet(t *testing.T) {
  function TestList (line 65) | func TestList(t *testing.T) {
  function startTestServer (line 84) | func startTestServer(t *testing.T, ctx context.Context) string {
  function getFreePort (line 110) | func getFreePort() (string, error) {
  function getPort (line 139) | func getPort(addr fmt.Stringer) (string, error) {

FILE: ch12/acme/wire.go
  function initializeServer (line 16) | func initializeServer() (*rest.Server, error) {
  function initializeServerCustomConfig (line 21) | func initializeServerCustomConfig(_ exchange.Config, _ get.Config, _ lis...

FILE: ch12/acme/wire_gen.go
  function initializeServer (line 19) | func initializeServer() (*rest.Server, error) {
  function initializeServerCustomConfig (line 32) | func initializeServerCustomConfig(exchangeConfig exchange.Config, getCon...

FILE: ch12/fake.go
  function init (line 3) | func init() {

FILE: fake.go
  function init (line 3) | func init() {

FILE: resources/create.sql
  type `acme` (line 3) | CREATE TABLE IF NOT EXISTS `acme`.`person` (

FILE: vendor/github.com/DATA-DOG/go-sqlmock/argument.go
  type Argument (line 8) | type Argument interface
  function AnyArg (line 16) | func AnyArg() Argument {
  type anyArgument (line 20) | type anyArgument struct
    method Match (line 22) | func (a anyArgument) Match(_ driver.Value) bool {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/driver.go
  function init (line 12) | func init() {
  type mockDriver (line 19) | type mockDriver struct
    method Open (line 25) | func (d *mockDriver) Open(dsn string) (driver.Conn, error) {
  function New (line 42) | func New() (*sql.DB, Sqlmock, error) {
  function NewWithDSN (line 67) | func NewWithDSN(dsn string) (*sql.DB, Sqlmock, error) {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/expectations.go
  type expectation (line 13) | type expectation interface
  type commonExpectation (line 22) | type commonExpectation struct
    method fulfilled (line 28) | func (e *commonExpectation) fulfilled() bool {
  type ExpectedClose (line 34) | type ExpectedClose struct
    method WillReturnError (line 39) | func (e *ExpectedClose) WillReturnError(err error) *ExpectedClose {
    method String (line 45) | func (e *ExpectedClose) String() string {
  type ExpectedBegin (line 55) | type ExpectedBegin struct
    method WillReturnError (line 61) | func (e *ExpectedBegin) WillReturnError(err error) *ExpectedBegin {
    method String (line 67) | func (e *ExpectedBegin) String() string {
    method WillDelayFor (line 77) | func (e *ExpectedBegin) WillDelayFor(duration time.Duration) *Expected...
  type ExpectedCommit (line 84) | type ExpectedCommit struct
    method WillReturnError (line 89) | func (e *ExpectedCommit) WillReturnError(err error) *ExpectedCommit {
    method String (line 95) | func (e *ExpectedCommit) String() string {
  type ExpectedRollback (line 105) | type ExpectedRollback struct
    method WillReturnError (line 110) | func (e *ExpectedRollback) WillReturnError(err error) *ExpectedRollback {
    method String (line 116) | func (e *ExpectedRollback) String() string {
  type ExpectedQuery (line 127) | type ExpectedQuery struct
    method WithArgs (line 136) | func (e *ExpectedQuery) WithArgs(args ...driver.Value) *ExpectedQuery {
    method WillReturnError (line 142) | func (e *ExpectedQuery) WillReturnError(err error) *ExpectedQuery {
    method WillDelayFor (line 149) | func (e *ExpectedQuery) WillDelayFor(duration time.Duration) *Expected...
    method String (line 155) | func (e *ExpectedQuery) String() string {
  type ExpectedExec (line 182) | type ExpectedExec struct
    method WithArgs (line 191) | func (e *ExpectedExec) WithArgs(args ...driver.Value) *ExpectedExec {
    method WillReturnError (line 197) | func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec {
    method WillDelayFor (line 204) | func (e *ExpectedExec) WillDelayFor(duration time.Duration) *ExpectedE...
    method String (line 210) | func (e *ExpectedExec) String() string {
    method WillReturnResult (line 246) | func (e *ExpectedExec) WillReturnResult(result driver.Result) *Expecte...
  type ExpectedPrepare (line 253) | type ExpectedPrepare struct
    method WillReturnError (line 265) | func (e *ExpectedPrepare) WillReturnError(err error) *ExpectedPrepare {
    method WillReturnCloseError (line 271) | func (e *ExpectedPrepare) WillReturnCloseError(err error) *ExpectedPre...
    method WillDelayFor (line 278) | func (e *ExpectedPrepare) WillDelayFor(duration time.Duration) *Expect...
    method WillBeClosed (line 285) | func (e *ExpectedPrepare) WillBeClosed() *ExpectedPrepare {
    method ExpectQuery (line 292) | func (e *ExpectedPrepare) ExpectQuery() *ExpectedQuery {
    method ExpectExec (line 301) | func (e *ExpectedPrepare) ExpectExec() *ExpectedExec {
    method String (line 309) | func (e *ExpectedPrepare) String() string {
  type queryBasedExpectation (line 326) | type queryBasedExpectation struct
    method attemptMatch (line 332) | func (e *queryBasedExpectation) attemptMatch(sql string, args []namedV...
    method queryMatches (line 351) | func (e *queryBasedExpectation) queryMatches(sql string) bool {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/expectations_before_go18.go
  method WillReturnRows (line 13) | func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery {
  method argsMatches (line 18) | func (e *queryBasedExpectation) argsMatches(args []namedValue) error {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/expectations_go18.go
  method WillReturnRows (line 14) | func (e *ExpectedQuery) WillReturnRows(rows ...*Rows) *ExpectedQuery {
  method argsMatches (line 23) | func (e *queryBasedExpectation) argsMatches(args []namedValue) error {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/result.go
  type result (line 10) | type result struct
    method LastInsertId (line 33) | func (r *result) LastInsertId() (int64, error) {
    method RowsAffected (line 37) | func (r *result) RowsAffected() (int64, error) {
  function NewResult (line 18) | func NewResult(lastInsertID int64, rowsAffected int64) driver.Result {
  function NewErrorResult (line 27) | func NewErrorResult(err error) driver.Result {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/rows.go
  type rowSets (line 22) | type rowSets struct
    method Columns (line 27) | func (rs *rowSets) Columns() []string {
    method Close (line 31) | func (rs *rowSets) Close() error {
    method Next (line 36) | func (rs *rowSets) Next(dest []driver.Value) error {
    method String (line 51) | func (rs *rowSets) String() string {
    method empty (line 72) | func (rs *rowSets) empty() bool {
  type Rows (line 83) | type Rows struct
    method CloseError (line 105) | func (r *Rows) CloseError(err error) *Rows {
    method RowError (line 113) | func (r *Rows) RowError(row int, err error) *Rows {
    method AddRow (line 122) | func (r *Rows) AddRow(values ...driver.Value) *Rows {
    method FromCSVString (line 140) | func (r *Rows) FromCSVString(s string) *Rows {
  function NewRows (line 94) | func NewRows(columns []string) *Rows {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/rows_go18.go
  method HasNextResultSet (line 8) | func (rs *rowSets) HasNextResultSet() bool {
  method NextResultSet (line 13) | func (rs *rowSets) NextResultSet() error {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/sqlmock.go
  type Sqlmock (line 24) | type Sqlmock interface
  type sqlmock (line 78) | type sqlmock struct
    method open (line 87) | func (c *sqlmock) open() (*sql.DB, Sqlmock, error) {
    method ExpectClose (line 95) | func (c *sqlmock) ExpectClose() *ExpectedClose {
    method MatchExpectationsInOrder (line 101) | func (c *sqlmock) MatchExpectationsInOrder(b bool) {
    method Close (line 109) | func (c *sqlmock) Close() error {
    method ExpectationsWereMet (line 152) | func (c *sqlmock) ExpectationsWereMet() error {
    method Begin (line 169) | func (c *sqlmock) Begin() (driver.Tx, error) {
    method begin (line 181) | func (c *sqlmock) begin() (*ExpectedBegin, error) {
    method ExpectBegin (line 216) | func (c *sqlmock) ExpectBegin() *ExpectedBegin {
    method Exec (line 223) | func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Resu...
    method exec (line 243) | func (c *sqlmock) exec(query string, args []namedValue) (*ExpectedExec...
    method ExpectExec (line 300) | func (c *sqlmock) ExpectExec(sqlRegexStr string) *ExpectedExec {
    method Prepare (line 309) | func (c *sqlmock) Prepare(query string) (driver.Stmt, error) {
    method prepare (line 321) | func (c *sqlmock) prepare(query string) (*ExpectedPrepare, error) {
    method ExpectPrepare (line 370) | func (c *sqlmock) ExpectPrepare(sqlRegexStr string) *ExpectedPrepare {
    method Query (line 384) | func (c *sqlmock) Query(query string, args []driver.Value) (driver.Row...
    method query (line 404) | func (c *sqlmock) query(query string, args []namedValue) (*ExpectedQue...
    method ExpectQuery (line 462) | func (c *sqlmock) ExpectQuery(sqlRegexStr string) *ExpectedQuery {
    method ExpectCommit (line 470) | func (c *sqlmock) ExpectCommit() *ExpectedCommit {
    method ExpectRollback (line 476) | func (c *sqlmock) ExpectRollback() *ExpectedRollback {
    method Commit (line 483) | func (c *sqlmock) Commit() error {
    method Rollback (line 518) | func (c *sqlmock) Rollback() error {
  type namedValue (line 377) | type namedValue struct

FILE: vendor/github.com/DATA-DOG/go-sqlmock/sqlmock_go18.go
  method QueryContext (line 15) | func (c *sqlmock) QueryContext(ctx context.Context, query string, args [...
  method ExecContext (line 38) | func (c *sqlmock) ExecContext(ctx context.Context, query string, args []...
  method BeginTx (line 61) | func (c *sqlmock) BeginTx(ctx context.Context, opts driver.TxOptions) (d...
  method PrepareContext (line 79) | func (c *sqlmock) PrepareContext(ctx context.Context, query string) (dri...
  method Ping (line 99) | func (c *sqlmock) Ping(ctx context.Context) error {
  method ExecContext (line 104) | func (stmt *statement) ExecContext(ctx context.Context, args []driver.Na...
  method QueryContext (line 109) | func (stmt *statement) QueryContext(ctx context.Context, args []driver.N...

FILE: vendor/github.com/DATA-DOG/go-sqlmock/statement.go
  type statement (line 7) | type statement struct
    method Close (line 13) | func (stmt *statement) Close() error {
    method NumInput (line 18) | func (stmt *statement) NumInput() int {
    method Exec (line 22) | func (stmt *statement) Exec(args []driver.Value) (driver.Result, error) {
    method Query (line 26) | func (stmt *statement) Query(args []driver.Value) (driver.Rows, error) {

FILE: vendor/github.com/DATA-DOG/go-sqlmock/util.go
  function stripQuery (line 11) | func stripQuery(q string) (s string) {

FILE: vendor/github.com/davecgh/go-spew/spew/bypass.go
  constant UnsafeDisabled (line 31) | UnsafeDisabled = false
  constant ptrSize (line 34) | ptrSize = unsafe.Sizeof((*byte)(nil))
  function init (line 66) | func init() {
  function unsafeReflectValue (line 122) | func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {

FILE: vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
  constant UnsafeDisabled (line 28) | UnsafeDisabled = true
  function unsafeReflectValue (line 36) | func unsafeReflectValue(v reflect.Value) reflect.Value {

FILE: vendor/github.com/davecgh/go-spew/spew/common.go
  function catchPanic (line 72) | func catchPanic(w io.Writer, v reflect.Value) {
  function handleMethods (line 85) | func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handl...
  function printBool (line 144) | func printBool(w io.Writer, val bool) {
  function printInt (line 153) | func printInt(w io.Writer, val int64, base int) {
  function printUint (line 158) | func printUint(w io.Writer, val uint64, base int) {
  function printFloat (line 164) | func printFloat(w io.Writer, val float64, precision int) {
  function printComplex (line 170) | func printComplex(w io.Writer, c complex128, floatPrecision int) {
  function printHexPtr (line 185) | func printHexPtr(w io.Writer, p uintptr) {
  type valuesSorter (line 219) | type valuesSorter struct
    method Len (line 279) | func (s *valuesSorter) Len() int {
    method Swap (line 285) | func (s *valuesSorter) Swap(i, j int) {
    method Less (line 326) | func (s *valuesSorter) Less(i, j int) bool {
  function newValuesSorter (line 228) | func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Inter...
  function canSortSimply (line 256) | func canSortSimply(kind reflect.Kind) bool {
  function valueSortLess (line 295) | func valueSortLess(a, b reflect.Value) bool {
  function sortValues (line 336) | func sortValues(values []reflect.Value, cs *ConfigState) {

FILE: vendor/github.com/davecgh/go-spew/spew/config.go
  type ConfigState (line 37) | type ConfigState struct
    method Errorf (line 115) | func (c *ConfigState) Errorf(format string, a ...interface{}) (err err...
    method Fprint (line 127) | func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, er...
    method Fprintf (line 139) | func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interfa...
    method Fprintln (line 150) | func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, ...
    method Print (line 162) | func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
    method Printf (line 174) | func (c *ConfigState) Printf(format string, a ...interface{}) (n int, ...
    method Println (line 186) | func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
    method Sprint (line 197) | func (c *ConfigState) Sprint(a ...interface{}) string {
    method Sprintf (line 208) | func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
    method Sprintln (line 219) | func (c *ConfigState) Sprintln(a ...interface{}) string {
    method NewFormatter (line 240) | func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
    method Fdump (line 246) | func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
    method Dump (line 273) | func (c *ConfigState) Dump(a ...interface{}) {
    method Sdump (line 279) | func (c *ConfigState) Sdump(a ...interface{}) string {
    method convertArgs (line 288) | func (c *ConfigState) convertArgs(args []interface{}) (formatters []in...
  function NewDefaultConfig (line 304) | func NewDefaultConfig() *ConfigState {

FILE: vendor/github.com/davecgh/go-spew/spew/dump.go
  type dumpState (line 51) | type dumpState struct
    method indent (line 62) | func (d *dumpState) indent() {
    method unpackValue (line 73) | func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
    method dumpPtr (line 81) | func (d *dumpState) dumpPtr(v reflect.Value) {
    method dumpSlice (line 161) | func (d *dumpState) dumpSlice(v reflect.Value) {
    method dump (line 251) | func (d *dumpState) dump(v reflect.Value) {
  function fdump (line 453) | func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
  function Fdump (line 472) | func Fdump(w io.Writer, a ...interface{}) {
  function Sdump (line 478) | func Sdump(a ...interface{}) string {
  function Dump (line 507) | func Dump(a ...interface{}) {

FILE: vendor/github.com/davecgh/go-spew/spew/format.go
  constant supportedFlags (line 28) | supportedFlags = "0-+# "
  type formatState (line 34) | type formatState struct
    method buildDefaultFormat (line 47) | func (f *formatState) buildDefaultFormat() (format string) {
    method constructOrigFormat (line 65) | func (f *formatState) constructOrigFormat(verb rune) (format string) {
    method unpackValue (line 94) | func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
    method formatPtr (line 105) | func (f *formatState) formatPtr(v reflect.Value) {
    method format (line 201) | func (f *formatState) format(v reflect.Value) {
    method Format (line 371) | func (f *formatState) Format(fs fmt.State, verb rune) {
  function newFormatter (line 394) | func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
  function NewFormatter (line 417) | func NewFormatter(v interface{}) fmt.Formatter {

FILE: vendor/github.com/davecgh/go-spew/spew/spew.go
  function Errorf (line 32) | func Errorf(format string, a ...interface{}) (err error) {
  function Fprint (line 44) | func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
  function Fprintf (line 56) | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err e...
  function Fprintln (line 67) | func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
  function Print (line 79) | func Print(a ...interface{}) (n int, err error) {
  function Printf (line 91) | func Printf(format string, a ...interface{}) (n int, err error) {
  function Println (line 103) | func Println(a ...interface{}) (n int, err error) {
  function Sprint (line 114) | func Sprint(a ...interface{}) string {
  function Sprintf (line 125) | func Sprintf(format string, a ...interface{}) string {
  function Sprintln (line 136) | func Sprintln(a ...interface{}) string {
  function convertArgs (line 142) | func convertArgs(args []interface{}) (formatters []interface{}) {

FILE: vendor/github.com/go-sql-driver/mysql/appengine.go
  function init (line 17) | func init() {

FILE: vendor/github.com/go-sql-driver/mysql/buffer.go
  constant defaultBufSize (line 17) | defaultBufSize = 4096
  type buffer (line 24) | type buffer struct
    method fill (line 41) | func (b *buffer) fill(need int) error {
    method readNext (line 94) | func (b *buffer) readNext(need int) ([]byte, error) {
    method takeBuffer (line 112) | func (b *buffer) takeBuffer(length int) []byte {
    method takeSmallBuffer (line 132) | func (b *buffer) takeSmallBuffer(length int) []byte {
    method takeCompleteBuffer (line 142) | func (b *buffer) takeCompleteBuffer() []byte {
  function newBuffer (line 32) | func newBuffer(nc net.Conn) buffer {

FILE: vendor/github.com/go-sql-driver/mysql/collations.go
  constant defaultCollation (line 11) | defaultCollation = "utf8_general_ci"
  constant binaryCollation (line 12) | binaryCollation = "binary"

FILE: vendor/github.com/go-sql-driver/mysql/connection.go
  type mysqlContext (line 21) | type mysqlContext interface
  type mysqlConn (line 30) | type mysqlConn struct
    method handleParams (line 54) | func (mc *mysqlConn) handleParams() (err error) {
    method markBadConn (line 83) | func (mc *mysqlConn) markBadConn(err error) error {
    method Begin (line 93) | func (mc *mysqlConn) Begin() (driver.Tx, error) {
    method begin (line 97) | func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
    method Close (line 115) | func (mc *mysqlConn) Close() (err error) {
    method cleanup (line 130) | func (mc *mysqlConn) cleanup() {
    method error (line 145) | func (mc *mysqlConn) error() error {
    method Prepare (line 155) | func (mc *mysqlConn) Prepare(query string) (driver.Stmt, err
Condensed preview — 656 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,775K chars).
[
  {
    "path": ".gitignore",
    "chars": 268,
    "preview": "# Created by .ignore support plugin (hsz.mobi)\n### Go template\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*."
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2018 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "README.md",
    "chars": 8170,
    "preview": "\n\n\n# Hands-On Dependency Injection in Go\r\n\r\n<a href=\"https://www.packtpub.com/application-development/hands-dependency-i"
  },
  {
    "path": "ch01/01_defining_depenency_injection/01_interface.go",
    "chars": 904,
    "preview": "package defining_depenency_injection\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n)\n\n// Saver persists the supplied bytes\ntype S"
  },
  {
    "path": "ch01/01_defining_depenency_injection/02_function_literal.go",
    "chars": 676,
    "preview": "package defining_depenency_injection\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// LoadPerson will load the requested person by ID.\n/"
  },
  {
    "path": "ch01/01_defining_depenency_injection/03_test_without_nfs_test.go",
    "chars": 705,
    "preview": "package defining_depenency_injection\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/t"
  },
  {
    "path": "ch01/01_defining_depenency_injection/04_fail_test_without_nfs_test.go",
    "chars": 538,
    "preview": "package defining_depenency_injection\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/"
  },
  {
    "path": "ch01/02_code_smells/01_code_bloat/01_switch_type.go",
    "chars": 510,
    "preview": "package code_bloat\n\nimport (\n\t\"strconv\"\n)\n\nfunc AppendValue(buffer []byte, in interface{}) []byte {\n\tvar value []byte\n\n\t"
  },
  {
    "path": "ch01/02_code_smells/02_resistance_to_change/01_shotgun_surgey.go",
    "chars": 585,
    "preview": "package _2_resistance_to_change\n\nimport (\n\t\"database/sql\"\n\t\"io\"\n)\n\n// Renderer will render a person to the supplied writ"
  },
  {
    "path": "ch01/02_code_smells/03_wasted_effort/01_excessive_comments.go",
    "chars": 773,
    "preview": "package wasted_effort\n\n// Excessive comments\nfunc outputOrderedPeopleA(in []*Person) {\n\t// This code orders people by na"
  },
  {
    "path": "ch01/02_code_smells/03_wasted_effort/02_complicated_go.go",
    "chars": 271,
    "preview": "package wasted_effort\n\nimport (\n\t\"image\"\n\t\"image/color\"\n\t\"math\"\n)\n\nfunc d(r, v float64, i *image.RGBA, c color.Color) {\n"
  },
  {
    "path": "ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config/config.go",
    "chars": 633,
    "preview": "// +build bad\n\npackage config\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch01"
  },
  {
    "path": "ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment/currency.go",
    "chars": 497,
    "preview": "// +build bad\n\npackage payment\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch0"
  },
  {
    "path": "ch01/02_code_smells/04_tight_coupling/02_object_orgy.go",
    "chars": 1070,
    "preview": "package _4_tight_coupling\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\ntype PageLoader struct {\n}\n\nfunc (o *PageLoade"
  },
  {
    "path": "ch01/02_code_smells/04_tight_coupling/03_feature_envy.go",
    "chars": 1336,
    "preview": "package _4_tight_coupling\n\nimport (\n\t\"errors\"\n\t\"time\"\n)\n\ntype searchRequest struct {\n\tquery string\n\tstart time.Time\n\tend"
  },
  {
    "path": "ch02/01_single_responsibility_principle/01_responsibility_vs_change.go",
    "chars": 606,
    "preview": "package srp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// CalculatorV1 calculates the test coverage for a directory and it's sub-director"
  },
  {
    "path": "ch02/01_single_responsibility_principle/02_responsibility_vs_change.go",
    "chars": 813,
    "preview": "package srp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// CalculatorV2 calculates the test coverage for a directory and it's sub-director"
  },
  {
    "path": "ch02/01_single_responsibility_principle/03_responsibility_vs_change.go",
    "chars": 1011,
    "preview": "package srp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// CalculatorV3 calculates the test coverage for a directory and it's sub-director"
  },
  {
    "path": "ch02/01_single_responsibility_principle/04_long_method.go",
    "chars": 853,
    "preview": "package srp\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc loadUserHandlerLong(resp http.Resp"
  },
  {
    "path": "ch02/01_single_responsibility_principle/04_long_method_test.go",
    "chars": 872,
    "preview": "package srp\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\""
  },
  {
    "path": "ch02/01_single_responsibility_principle/05_srp_method.go",
    "chars": 1059,
    "preview": "package srp\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc loadUserHandlerSRP(resp http.ResponseWriter, req *"
  },
  {
    "path": "ch02/02_open_closed_principle/01_open_closed_failure.go",
    "chars": 848,
    "preview": "package ocp\n\nimport (\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc BuildOutputOCPFail(response http.ResponseWriter, format string, person P"
  },
  {
    "path": "ch02/02_open_closed_principle/02_open_closed_success.go",
    "chars": 883,
    "preview": "package ocp\n\nimport (\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc BuildOutputOCPSuccess(response http.ResponseWriter, formatter PersonForm"
  },
  {
    "path": "ch02/02_open_closed_principle/03_shotgun_surgery.go",
    "chars": 1019,
    "preview": "package ocp\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc GetUserHandlerV1(resp http.ResponseWriter, req *http.Request) {\n\t//"
  },
  {
    "path": "ch02/02_open_closed_principle/04_after_shotgun_surgery.go",
    "chars": 1023,
    "preview": "package ocp\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n)\n\nfunc GetUserHandlerV2(resp http.ResponseWriter, req"
  },
  {
    "path": "ch02/02_open_closed_principle/05_composition.go",
    "chars": 1150,
    "preview": "package ocp\n\nimport (\n\t\"database/sql\"\n)\n\ntype rowConverter struct {\n}\n\n// populate the supplied Person from *sql.Row or "
  },
  {
    "path": "ch02/02_open_closed_principle/06_handler_struct.go",
    "chars": 314,
    "preview": "package ocp\n\nimport (\n\t\"net/http\"\n)\n\n// a HTTP health check handler in long form\ntype healthCheckLong struct {\n}\n\nfunc ("
  },
  {
    "path": "ch02/02_open_closed_principle/07_handler_func.go",
    "chars": 285,
    "preview": "package ocp\n\nimport (\n\t\"net/http\"\n)\n\n// a HTTP health check handler in short form\nfunc healthCheckShort(resp http.Respon"
  },
  {
    "path": "ch02/03_liskov_substitution_principle/01_violation/example.go",
    "chars": 653,
    "preview": "package lsp_violation\n\nfunc Go(vehicle actions) {\n\tif sled, ok := vehicle.(*Sled); ok {\n\t\tsled.pushStart()\n\t} else {\n\t\tv"
  },
  {
    "path": "ch02/03_liskov_substitution_principle/02_fixed/example.go",
    "chars": 670,
    "preview": "package fixedv1\n\nfunc Go(vehicle actions) {\n\tswitch concrete := vehicle.(type) {\n\tcase poweredActions:\n\t\tconcrete.startE"
  },
  {
    "path": "ch02/03_liskov_substitution_principle/03_fixed/example.go",
    "chars": 482,
    "preview": "package fixedv2\n\nfunc Go(vehicle actions) {\n\tvehicle.start()\n\tvehicle.drive()\n}\n\ntype actions interface {\n\tstart()\n\tdriv"
  },
  {
    "path": "ch02/03_liskov_substitution_principle/04_behaviour.go",
    "chars": 452,
    "preview": "package lsp\n\ntype Collection interface {\n\tAdd(item interface{})\n\tGet(index int) interface{}\n}\n\ntype CollectionImpl struc"
  },
  {
    "path": "ch02/03_liskov_substitution_principle/05_behaviour_fixed.go",
    "chars": 452,
    "preview": "package lsp\n\ntype ImmutableCollection interface {\n\tGet(index int) interface{}\n}\n\ntype MutableCollection interface {\n\tImm"
  },
  {
    "path": "ch02/04_interface_segregation_principle/01_fat_interface.go",
    "chars": 1165,
    "preview": "package isp\n\nimport (\n\t\"context\"\n)\n\ntype Item struct {\n\tKey     string\n\tPayload []byte\n}\n\ntype FatDbInterface interface "
  },
  {
    "path": "ch02/04_interface_segregation_principle/02_thin_interface.go",
    "chars": 406,
    "preview": "package isp\n\ntype myDB interface {\n\tGetItem(ID int) (Item, error)\n\tPutItem(item Item) error\n}\n\ntype CacheV2 struct {\n\tdb"
  },
  {
    "path": "ch02/04_interface_segregation_principle/03_repeated_inputs.go",
    "chars": 847,
    "preview": "package isp\n\nimport (\n\t\"context\"\n\t\"errors\"\n)\n\nfunc Encrypt(ctx context.Context, data []byte) ([]byte, error) {\n\t// As th"
  },
  {
    "path": "ch02/04_interface_segregation_principle/04_repeated_inputs.go",
    "chars": 879,
    "preview": "package isp\n\nimport (\n\t\"errors\"\n)\n\ntype Value interface {\n\tValue(key interface{}) interface{}\n}\n\ntype Monitor interface "
  },
  {
    "path": "ch02/04_interface_segregation_principle/05_repeated_inputs.go",
    "chars": 303,
    "preview": "package isp\n\nimport (\n\t\"context\"\n)\n\nfunc UseEncryptV2() {\n\t// create a context\n\tctx, cancel := context.WithCancel(contex"
  },
  {
    "path": "ch02/04_interface_segregation_principle/06_implicit_interfaces.go",
    "chars": 289,
    "preview": "package isp\n\nimport (\n\t\"fmt\"\n)\n\ntype Talker interface {\n\tSayHello() string\n}\n\ntype Dog struct{}\n\n// The method implicitl"
  },
  {
    "path": "ch03/01_optimizing_for_humans/01_not_so_simple.go",
    "chars": 430,
    "preview": "package humans\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc NotSoSimple(ID int64, name string, age int, registered b"
  },
  {
    "path": "ch03/01_optimizing_for_humans/02_start_simple.go",
    "chars": 250,
    "preview": "package humans\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nfunc Simpler(ID int64, name string, age int, registered bool) string {\n\tna"
  },
  {
    "path": "ch03/01_optimizing_for_humans/03_too_abstract.go",
    "chars": 317,
    "preview": "package humans\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\ntype myGetter interface {\n\tGet(url string) (*http.Response, error)\n"
  },
  {
    "path": "ch03/01_optimizing_for_humans/04_common_concept.go",
    "chars": 230,
    "preview": "package humans\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\nfunc CommonConcept(url string) ([]byte, error) {\n\tresp, err := http"
  },
  {
    "path": "ch03/01_optimizing_for_humans/05_boolean_param.go",
    "chars": 254,
    "preview": "package humans\n\nimport (\n\t\"time\"\n)\n\ntype Pet struct {\n\tName string\n\tDog  bool\n\tBorn time.Time\n}\n\nfunc NewPet(name string"
  },
  {
    "path": "ch03/01_optimizing_for_humans/06_hidden_boolean.go",
    "chars": 220,
    "preview": "package humans\n\nconst (\n\tisDog = true\n\tisCat = false\n)\n\nfunc NewDog(name string) Pet {\n\treturn NewPet(name, isDog)\n}\n\nfu"
  },
  {
    "path": "ch03/01_optimizing_for_humans/07_wide_formatter.go",
    "chars": 155,
    "preview": "package humans\n\ntype WideFormatter interface {\n\tToCSV(pets []Pet) ([]byte, error)\n\tToGOB(pets []Pet) ([]byte, error)\n\tTo"
  },
  {
    "path": "ch03/01_optimizing_for_humans/08_thin_formatters.go",
    "chars": 508,
    "preview": "package humans\n\ntype ThinFormatter interface {\n\tFormat(pets []Pet) ([]byte, error)\n}\n\ntype CSVFormatter struct{}\n\nfunc ("
  },
  {
    "path": "ch03/01_optimizing_for_humans/09_extra_config.go",
    "chars": 427,
    "preview": "package humans\n\n// PetFetcher searches the data store for pets whos name matches the search string.\n// Limit is optional"
  },
  {
    "path": "ch03/02_unit_tests/01_loader.go",
    "chars": 1417,
    "preview": "package unit_tests\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype L"
  },
  {
    "path": "ch03/02_unit_tests/02_language_feature.go",
    "chars": 290,
    "preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype Pet struct {\n\tName string\n}\n\nfunc"
  },
  {
    "path": "ch03/02_unit_tests/03_simple_test.go",
    "chars": 268,
    "preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc concat(a, b string) string {\n\tret"
  },
  {
    "path": "ch03/02_unit_tests/04_test_from_api.go",
    "chars": 627,
    "preview": "package unit_tests\n\nimport (\n\t\"database/sql\"\n)\n\ntype PetSaver struct{}\n\n// save the supplied pet and return the ID\nfunc "
  },
  {
    "path": "ch03/02_unit_tests/05_repeated_code.go",
    "chars": 570,
    "preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Round the supplied number to the ne"
  },
  {
    "path": "ch03/02_unit_tests/06_tdt.go",
    "chars": 536,
    "preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestRound(t *testing.T) {\n\tscenar"
  },
  {
    "path": "ch03/02_unit_tests/07_person_loader.go",
    "chars": 432,
    "preview": "package unit_tests\n\nimport (\n\t\"errors\"\n)\n\nvar ErrNotFound = errors.New(\"person not found\")\n\ntype Person struct {\n\tName s"
  },
  {
    "path": "ch03/02_unit_tests/08_stub.go",
    "chars": 213,
    "preview": "package unit_tests\n\n// Stubbed implementation of PersonLoader\ntype PersonLoaderStub struct {\n\tPerson *Person\n\tError  err"
  },
  {
    "path": "ch03/02_unit_tests/09_stub_tdt.go",
    "chars": 1048,
    "preview": "package unit_tests\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestLoadPersonNameStubs"
  },
  {
    "path": "ch03/02_unit_tests/10_mocks.go",
    "chars": 1773,
    "preview": "package unit_tests\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/m"
  },
  {
    "path": "ch03/03_test_induced_damage/01_io_closer.go",
    "chars": 234,
    "preview": "package test_damage\n\nimport (\n\t\"io\"\n)\n\nfunc WriteAndClose(destination io.WriteCloser, contents string) error {\n\tdefer de"
  },
  {
    "path": "ch03/03_test_induced_damage/02_json.go",
    "chars": 262,
    "preview": "package test_damage\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\nfunc PrintAsJSON(destination io.Writer, plant Plant) error {\n\tby"
  },
  {
    "path": "ch03/04_visualizing_dependencies/depgraph.sh",
    "chars": 771,
    "preview": "#!/usr/bin/env bash\n\n# Note:\n# This script should be run in the base directory of the project/service\n\n# Inputs\n#\n# This"
  },
  {
    "path": "ch03/fake.go",
    "chars": 171,
    "preview": "package Hands_On_Dependency_Injection_in_Go\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) w"
  },
  {
    "path": "ch04/01_welcome/01_bad_names.go",
    "chars": 76,
    "preview": "package welcome\n\ntype HouseV1 struct {\n\ta string\n\tb int\n\tt int\n\tp float64\n}\n"
  },
  {
    "path": "ch04/01_welcome/02_improved_names.go",
    "chars": 104,
    "preview": "package welcome\n\ntype HouseV2 struct {\n\taddress  string\n\tbedrooms int\n\ttoilets  int\n\tprice    float64\n}\n"
  },
  {
    "path": "ch04/01_welcome/03_long_method.go",
    "chars": 848,
    "preview": "package welcome\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc longMethod(resp http.ResponseW"
  },
  {
    "path": "ch04/01_welcome/04_long_method_test.go",
    "chars": 1078,
    "preview": "package welcome\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-"
  },
  {
    "path": "ch04/01_welcome/05_short_methods.go",
    "chars": 1037,
    "preview": "package welcome\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc shortMethods(resp http.ResponseWriter, req *ht"
  },
  {
    "path": "ch04/03_known_issues/01_data_and_rest/get_example.go",
    "chars": 418,
    "preview": "//+build ignore\n\npackage data_and_rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependenc"
  },
  {
    "path": "ch04/03_known_issues/02_config_coupling/config.go",
    "chars": 236,
    "preview": "package config_coupling\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/03_known_issues/"
  },
  {
    "path": "ch04/03_known_issues/02_config_coupling/currency/currency.go",
    "chars": 719,
    "preview": "package currency\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\n// Currency is a custom type; used for convenience and code readab"
  },
  {
    "path": "ch04/acme/internal/config/config.go",
    "chars": 1453,
    "preview": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection"
  },
  {
    "path": "ch04/acme/internal/config/config_test.go",
    "chars": 1058,
    "preview": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfun"
  },
  {
    "path": "ch04/acme/internal/logging/logging.go",
    "chars": 750,
    "preview": "package logging\n\nimport (\n\t\"fmt\"\n)\n\n// L is the global instance of the logger\nvar L = &LoggerStdOut{}\n\n// LoggerStdOut l"
  },
  {
    "path": "ch04/acme/internal/modules/data/data.go",
    "chars": 4304,
    "preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/"
  },
  {
    "path": "ch04/acme/internal/modules/data/data_test.go",
    "chars": 566,
    "preview": "package data\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc "
  },
  {
    "path": "ch04/acme/internal/modules/exchange/converter.go",
    "chars": 2853,
    "preview": "package exchange\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-"
  },
  {
    "path": "ch04/acme/internal/modules/get/get.go",
    "chars": 787,
    "preview": "package get\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/mod"
  },
  {
    "path": "ch04/acme/internal/modules/get/go_test.go",
    "chars": 379,
    "preview": "package get\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc T"
  },
  {
    "path": "ch04/acme/internal/modules/list/list.go",
    "chars": 1017,
    "preview": "package list\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/mo"
  },
  {
    "path": "ch04/acme/internal/modules/list/list_test.go",
    "chars": 307,
    "preview": "package list\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc "
  },
  {
    "path": "ch04/acme/internal/modules/register/register.go",
    "chars": 2886,
    "preview": "package register\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/interna"
  },
  {
    "path": "ch04/acme/internal/modules/register/register_test.go",
    "chars": 512,
    "preview": "package register\n\nimport (\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/intern"
  },
  {
    "path": "ch04/acme/internal/rest/common_test.go",
    "chars": 769,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net\"\n)\n\nfunc getOpenPort() (string, error) {\n\tlistener, err := net.Listen(\"tcp\", \"12"
  },
  {
    "path": "ch04/acme/internal/rest/get.go",
    "chars": 2826,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/PacktPublishing/Hand"
  },
  {
    "path": "ch04/acme/internal/rest/get_test.go",
    "chars": 960,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
  },
  {
    "path": "ch04/acme/internal/rest/list.go",
    "chars": 1735,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in"
  },
  {
    "path": "ch04/acme/internal/rest/list_test.go",
    "chars": 1157,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
  },
  {
    "path": "ch04/acme/internal/rest/not_found.go",
    "chars": 197,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n)\n\nfunc notFoundHandler(response http.ResponseWriter, _ *http.Request) {\n\tresponse.Wr"
  },
  {
    "path": "ch04/acme/internal/rest/not_found_test.go",
    "chars": 698,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNot"
  },
  {
    "path": "ch04/acme/internal/rest/register.go",
    "chars": 2091,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
  },
  {
    "path": "ch04/acme/internal/rest/register_test.go",
    "chars": 1235,
    "preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/"
  },
  {
    "path": "ch04/acme/internal/rest/server.go",
    "chars": 1402,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// New will create and initialize the server\nfunc New(a"
  },
  {
    "path": "ch04/acme/main.go",
    "chars": 388,
    "preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/c"
  },
  {
    "path": "ch04/fake.go",
    "chars": 140,
    "preview": "package ch04\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) will find Go code in this direct"
  },
  {
    "path": "ch05/02_advantages/01_function.go",
    "chars": 433,
    "preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\nfunc SaveConfig(filename string, cfg *Config) error"
  },
  {
    "path": "ch05/02_advantages/02_monkey_patched.go",
    "chars": 667,
    "preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\nfunc SaveConfigPatched(filename string, cfg "
  },
  {
    "path": "ch05/02_advantages/03_injected_lambda.go",
    "chars": 809,
    "preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n)\n\nfunc SaveConfigInjected(writer fileWri"
  },
  {
    "path": "ch05/02_advantages/04_as_object.go",
    "chars": 746,
    "preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n)\n\ntype ConfigSaver struct {\n\tFileWriter "
  },
  {
    "path": "ch05/02_advantages/05_math_rand.go",
    "chars": 1085,
    "preview": "package advantages\n\n// A Rand is a source of random numbers.\ntype Rand struct {\n\tsrc Source\n\n\t// code removed\n}\n\n// Int "
  },
  {
    "path": "ch05/02_advantages/06_math_rand_test.go",
    "chars": 551,
    "preview": "package advantages\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestInt(t *testing.T) {\n\t// monke"
  },
  {
    "path": "ch05/03_applying/01_simple_sqlmock_test.go",
    "chars": 1338,
    "preview": "package applying\n\nimport (\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretchr/testify/a"
  },
  {
    "path": "ch05/03_applying/02_load.go",
    "chars": 2238,
    "preview": "package applying\n\nimport (\n\t\"database/sql\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretch"
  },
  {
    "path": "ch05/04_disadvantages/01_verbose.go",
    "chars": 490,
    "preview": "package disadvantages\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\nfunc SaveConfig(filename string, cfg *Config) er"
  },
  {
    "path": "ch05/04_disadvantages/02_verbose_test.go",
    "chars": 620,
    "preview": "package disadvantages\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSaveConfig(t *testin"
  },
  {
    "path": "ch05/04_disadvantages/03_refactored_test.go",
    "chars": 831,
    "preview": "package disadvantages\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSaveConfig_refactore"
  },
  {
    "path": "ch05/acme/internal/config/config.go",
    "chars": 1453,
    "preview": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection"
  },
  {
    "path": "ch05/acme/internal/config/config_test.go",
    "chars": 1058,
    "preview": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfun"
  },
  {
    "path": "ch05/acme/internal/logging/logging.go",
    "chars": 750,
    "preview": "package logging\n\nimport (\n\t\"fmt\"\n)\n\n// L is the global instance of the logger\nvar L = &LoggerStdOut{}\n\n// LoggerStdOut l"
  },
  {
    "path": "ch05/acme/internal/modules/data/data.go",
    "chars": 4543,
    "preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/"
  },
  {
    "path": "ch05/acme/internal/modules/data/data_test.go",
    "chars": 5545,
    "preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/s"
  },
  {
    "path": "ch05/acme/internal/modules/exchange/converter.go",
    "chars": 2853,
    "preview": "package exchange\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-"
  },
  {
    "path": "ch05/acme/internal/modules/get/get.go",
    "chars": 873,
    "preview": "package get\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/mod"
  },
  {
    "path": "ch05/acme/internal/modules/get/go_test.go",
    "chars": 1989,
    "preview": "package get\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/i"
  },
  {
    "path": "ch05/acme/internal/modules/list/list.go",
    "chars": 1110,
    "preview": "package list\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/mo"
  },
  {
    "path": "ch05/acme/internal/modules/list/list_test.go",
    "chars": 1949,
    "preview": "package list\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/"
  },
  {
    "path": "ch05/acme/internal/modules/register/register.go",
    "chars": 2977,
    "preview": "package register\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/interna"
  },
  {
    "path": "ch05/acme/internal/modules/register/register_test.go",
    "chars": 1483,
    "preview": "package register\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/a"
  },
  {
    "path": "ch05/acme/internal/rest/common_test.go",
    "chars": 769,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net\"\n)\n\nfunc getOpenPort() (string, error) {\n\tlistener, err := net.Listen(\"tcp\", \"12"
  },
  {
    "path": "ch05/acme/internal/rest/get.go",
    "chars": 2826,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/PacktPublishing/Hand"
  },
  {
    "path": "ch05/acme/internal/rest/get_test.go",
    "chars": 960,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
  },
  {
    "path": "ch05/acme/internal/rest/list.go",
    "chars": 1735,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in"
  },
  {
    "path": "ch05/acme/internal/rest/list_test.go",
    "chars": 1157,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
  },
  {
    "path": "ch05/acme/internal/rest/not_found.go",
    "chars": 197,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n)\n\nfunc notFoundHandler(response http.ResponseWriter, _ *http.Request) {\n\tresponse.Wr"
  },
  {
    "path": "ch05/acme/internal/rest/not_found_test.go",
    "chars": 698,
    "preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNot"
  },
  {
    "path": "ch05/acme/internal/rest/register.go",
    "chars": 2091,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
  },
  {
    "path": "ch05/acme/internal/rest/register_test.go",
    "chars": 1235,
    "preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/"
  },
  {
    "path": "ch05/acme/internal/rest/server.go",
    "chars": 1402,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// New will create and initialize the server\nfunc New(a"
  },
  {
    "path": "ch05/acme/main.go",
    "chars": 388,
    "preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/c"
  },
  {
    "path": "ch05/fake.go",
    "chars": 140,
    "preview": "package ch05\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) will find Go code in this direct"
  },
  {
    "path": "ch06/01_constructor_injection/01_welcome_email.go",
    "chars": 987,
    "preview": "package constructor_injection\n\nimport (\n\t\"errors\"\n)\n\nfunc NewWelcomeSender(in *Mailer) (*WelcomeSender, error) {\n\t// gua"
  },
  {
    "path": "ch06/01_constructor_injection/01_welcome_email_test.go",
    "chars": 515,
    "preview": "package constructor_injection\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewWelcomeSender_h"
  },
  {
    "path": "ch06/01_constructor_injection/02_mailer_interface.go",
    "chars": 179,
    "preview": "package constructor_injection\n\n// Mailer sends and receives emails\ntype MailerInterface interface {\n\tSend(to string, bod"
  },
  {
    "path": "ch06/01_constructor_injection/03_sender_interface.go",
    "chars": 508,
    "preview": "package constructor_injection\n\ntype Sender interface {\n\tSend(to string, body string) error\n}\n\nfunc NewWelcomeSenderV2(in"
  },
  {
    "path": "ch06/01_constructor_injection/05_duck_typing.go",
    "chars": 313,
    "preview": "package constructor_injection\n\nimport (\n\t\"fmt\"\n)\n\ntype Talker interface {\n\tSpeak() string\n\tShout() string\n}\n\ntype Dog st"
  },
  {
    "path": "ch06/02_advantages/01_easy_to_implement.go",
    "chars": 462,
    "preview": "package advantages\n\n// WelcomeSender sends a Welcome email to new users\ntype WelcomeSender struct {\n\tMailer *Mailer\n}\n\nf"
  },
  {
    "path": "ch06/02_advantages/01_easy_to_implement_example_test.go",
    "chars": 274,
    "preview": "package advantages_test\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages\"\n)"
  },
  {
    "path": "ch06/02_advantages/02_easy_to_implement.go",
    "chars": 444,
    "preview": "package advantages\n\nfunc NewWelcomeSenderV2(mailer *Mailer) *WelcomeSenderV2 {\n\treturn &WelcomeSenderV2{\n\t\tmailer: maile"
  },
  {
    "path": "ch06/02_advantages/02_easy_to_implement_example_test.go",
    "chars": 266,
    "preview": "package advantages_test\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages\"\n)"
  },
  {
    "path": "ch06/02_advantages/03_predictable.go",
    "chars": 523,
    "preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\ntype Engine interface {\n\tStart()\n\tIncreasePower()\n\tDecreasePower()\n\tStop()\n\tIs"
  },
  {
    "path": "ch06/02_advantages/04_predictable.go",
    "chars": 467,
    "preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\nfunc NewCarV2(engine Engine) (*CarV2, error) {\n\tif engine == nil {\n\t\treturn ni"
  },
  {
    "path": "ch06/02_advantages/05_encapsulation.go",
    "chars": 308,
    "preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\nfunc (c *CarV2) FillPetrolTank() error {\n\t// use the engine\n\tif c.engine.IsRun"
  },
  {
    "path": "ch06/02_advantages/06_encapsulation.go",
    "chars": 256,
    "preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\nfunc (c *CarV2) FillPetrolTankV2(engine Engine) error {\n\t// use the engine\n\tif"
  },
  {
    "path": "ch06/03_applying/01/01_register_handler_before.go",
    "chars": 2077,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
  },
  {
    "path": "ch06/03_applying/01/data/person.go",
    "chars": 409,
    "preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
  },
  {
    "path": "ch06/03_applying/01/register/register.go",
    "chars": 579,
    "preview": "package register\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data\"\n)\n"
  },
  {
    "path": "ch06/03_applying/02/01_register_handler.go",
    "chars": 465,
    "preview": "package rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/02/register\"\n)\n"
  },
  {
    "path": "ch06/03_applying/02/data/person.go",
    "chars": 409,
    "preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
  },
  {
    "path": "ch06/03_applying/02/register/register.go",
    "chars": 579,
    "preview": "package register\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data\"\n)\n"
  },
  {
    "path": "ch06/03_applying/03/data/person.go",
    "chars": 409,
    "preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
  },
  {
    "path": "ch06/03_applying/03/mock_register_model_test.go",
    "chars": 726,
    "preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
  },
  {
    "path": "ch06/03_applying/03/register_test.go",
    "chars": 428,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestRegisterHandler_ServeHTTP(t *testing.T) {\n\tscenarios := []stru"
  },
  {
    "path": "ch06/03_applying/04/data/person.go",
    "chars": 409,
    "preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
  },
  {
    "path": "ch06/03_applying/04/mock_register_model_test.go",
    "chars": 726,
    "preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
  },
  {
    "path": "ch06/03_applying/04/register.go",
    "chars": 1006,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/04"
  },
  {
    "path": "ch06/03_applying/04/register_test.go",
    "chars": 1400,
    "preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretch"
  },
  {
    "path": "ch06/03_applying/05/data/person.go",
    "chars": 409,
    "preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
  },
  {
    "path": "ch06/03_applying/05/fakes.go",
    "chars": 1295,
    "preview": "package reset\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/0"
  },
  {
    "path": "ch06/03_applying/05/get/getter.go",
    "chars": 261,
    "preview": "package get\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data\"\n)\n\n// S"
  },
  {
    "path": "ch06/03_applying/05/list/lister.go",
    "chars": 258,
    "preview": "package list\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data\"\n)\n\n// "
  },
  {
    "path": "ch06/03_applying/05/register/registerer.go",
    "chars": 272,
    "preview": "package register\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data\"\n)\n"
  },
  {
    "path": "ch06/03_applying/05/server.go",
    "chars": 632,
    "preview": "package reset\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/get\"\n\t\"gith"
  },
  {
    "path": "ch06/04_disadvantages/01_lots_of_changes.go",
    "chars": 874,
    "preview": "package disadvantages\n\n// Dealer will shuffle a deck of cards and deal them to the players\nfunc DealCards() (player1 []C"
  },
  {
    "path": "ch06/04_disadvantages/02_overuse.go",
    "chars": 1017,
    "preview": "package disadvantages\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\nconst downstreamServer = \"http://www.exampl"
  },
  {
    "path": "ch06/04_disadvantages/03_non_obvious.go",
    "chars": 538,
    "preview": "package disadvantages\n\nimport (\n\t\"errors\"\n)\n\n// NewClient creates and initialises the client\nfunc NewClient(service DepS"
  },
  {
    "path": "ch06/04_disadvantages/04_non_obvious_example_test.go",
    "chars": 278,
    "preview": "package disadvantages_test\n\nfunc Example() {\n\n}\n\n// StubClient is a stub implementation of disadvantages.Client interfac"
  },
  {
    "path": "ch06/04_disadvantages/05_constructors.go",
    "chars": 523,
    "preview": "package disadvantages\n\ntype InnerService struct {\n\tinnerDep Dependency\n}\n\nfunc NewInnerService(innerDep Dependency) *Inn"
  },
  {
    "path": "ch06/acme/internal/config/config.go",
    "chars": 1453,
    "preview": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection"
  },
  {
    "path": "ch06/acme/internal/config/config_test.go",
    "chars": 1058,
    "preview": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfun"
  },
  {
    "path": "ch06/acme/internal/logging/logging.go",
    "chars": 750,
    "preview": "package logging\n\nimport (\n\t\"fmt\"\n)\n\n// L is the global instance of the logger\nvar L = &LoggerStdOut{}\n\n// LoggerStdOut l"
  },
  {
    "path": "ch06/acme/internal/modules/data/data.go",
    "chars": 4543,
    "preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/"
  },
  {
    "path": "ch06/acme/internal/modules/data/data_test.go",
    "chars": 5545,
    "preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/s"
  },
  {
    "path": "ch06/acme/internal/modules/exchange/converter.go",
    "chars": 2853,
    "preview": "package exchange\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-"
  },
  {
    "path": "ch06/acme/internal/modules/get/get.go",
    "chars": 873,
    "preview": "package get\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/mod"
  },
  {
    "path": "ch06/acme/internal/modules/get/go_test.go",
    "chars": 1989,
    "preview": "package get\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/i"
  },
  {
    "path": "ch06/acme/internal/modules/list/list.go",
    "chars": 1110,
    "preview": "package list\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/mo"
  },
  {
    "path": "ch06/acme/internal/modules/list/list_test.go",
    "chars": 1949,
    "preview": "package list\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/"
  },
  {
    "path": "ch06/acme/internal/modules/register/register.go",
    "chars": 2977,
    "preview": "package register\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/interna"
  },
  {
    "path": "ch06/acme/internal/modules/register/register_test.go",
    "chars": 1483,
    "preview": "package register\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/a"
  },
  {
    "path": "ch06/acme/internal/rest/get.go",
    "chars": 3120,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/PacktPublishing/Hand"
  },
  {
    "path": "ch06/acme/internal/rest/get_test.go",
    "chars": 4043,
    "preview": "package rest\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/"
  },
  {
    "path": "ch06/acme/internal/rest/list.go",
    "chars": 1967,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in"
  },
  {
    "path": "ch06/acme/internal/rest/list_test.go",
    "chars": 3057,
    "preview": "package rest\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/"
  },
  {
    "path": "ch06/acme/internal/rest/mock_get_model_test.go",
    "chars": 752,
    "preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
  },
  {
    "path": "ch06/acme/internal/rest/mock_list_model_test.go",
    "chars": 743,
    "preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
  },
  {
    "path": "ch06/acme/internal/rest/mock_register_model_test.go",
    "chars": 733,
    "preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
  },
  {
    "path": "ch06/acme/internal/rest/not_found.go",
    "chars": 197,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n)\n\nfunc notFoundHandler(response http.ResponseWriter, _ *http.Request) {\n\tresponse.Wr"
  },
  {
    "path": "ch06/acme/internal/rest/not_found_test.go",
    "chars": 374,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestN"
  },
  {
    "path": "ch06/acme/internal/rest/register.go",
    "chars": 2373,
    "preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
  },
  {
    "path": "ch06/acme/internal/rest/register_test.go",
    "chars": 3148,
    "preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.c"
  },
  {
    "path": "ch06/acme/internal/rest/server.go",
    "chars": 1511,
    "preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// New will create and initialize the server\nfunc New(a"
  },
  {
    "path": "ch06/acme/main.go",
    "chars": 841,
    "preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/c"
  },
  {
    "path": "ch06/fake.go",
    "chars": 140,
    "preview": "package ch06\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) will find Go code in this direct"
  },
  {
    "path": "ch06/pcov-html",
    "chars": 466,
    "preview": "#!/usr/bin/env bash\n\nif [ \"$1\" == \"\" ]; then\n\techo \"No input file. Usage: pcov-html ./your-package-dir/\"\n\texit 1\nfi\n\n# I"
  },
  {
    "path": "ch07/01_method_injection/01_fprint.go",
    "chars": 109,
    "preview": "package method_injection\n\nimport (\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc ExampleA() {\n\tfmt.Fprint(os.Stdout, \"Hello World\")\n}\n"
  },
  {
    "path": "ch07/01_method_injection/02_http_request.go",
    "chars": 304,
    "preview": "package method_injection\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc ExampleB() {\n\t// added to make the compiler happy"
  },
  {
    "path": "ch07/01_method_injection/03_fprint.go",
    "chars": 271,
    "preview": "package method_injection\n\nimport (\n\t\"io\"\n)\n\n// Fprint formats using the default formats for its operands and writes to w"
  },
  {
    "path": "ch07/01_method_injection/04_http_request.go",
    "chars": 705,
    "preview": "package method_injection\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nfunc NewRequest(method, url string, body"
  },
  {
    "path": "ch07/01_method_injection/05_timestamp_writer_v1.go",
    "chars": 309,
    "preview": "package method_injection\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// TimeStampWriterV1 will output the supplied message to writ"
  },
  {
    "path": "ch07/01_method_injection/06_timestamp_writer_v2.go",
    "chars": 406,
    "preview": "package method_injection\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// TimeStampWriterV2 will output the supplied messa"
  },
  {
    "path": "ch07/01_method_injection/07_timestamp_writer_v3.go",
    "chars": 389,
    "preview": "package method_injection\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n)\n\n// TimeStampWriterV3 will output the supplied message t"
  }
]

// ... and 456 more files (download for full content)

About this extraction

This page contains the full source code of the PacktPublishing/Hands-On-Dependency-Injection-in-Go GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 656 files (1.5 MB), approximately 436.7k tokens, and a symbol index with 3579 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!