func ValidateBookingAvailabilityResponse(req *pb.BookingAvailabilityRequest, resp *pb.BookingAvailabilityResponse) error {
	if err := checkRequired([]requiredTest{
		{"api_version", resp.GetApiVersion()},
		{"transaction_id", resp.GetTransactionId()},
		{"hotel_id", resp.GetHotelId()},
		{"party > adults", resp.GetParty().GetAdults()},
		{"hotel_details > name", resp.GetHotelDetails().GetName()},
		{"hotel_details > address > address1", resp.GetHotelDetails().GetAddress().GetAddress1()},
		{"hotel_details > address > city", resp.GetHotelDetails().GetAddress().GetCity()},
		{"hotel_details > address > province", resp.GetHotelDetails().GetAddress().GetProvince()},
	}); err != nil {
		return err
	}
	if err := validateFormat([]formatTest{
		{"start_date", resp.GetStartDate(), DateFormat},
		{"end_date", resp.GetEndDate(), DateFormat},
		{"hotel_details > address > country", resp.GetHotelDetails().GetAddress().GetCountry(), ISO3166},
	}); err != nil {
		return err
	}
	if err := compareFields([]validationTest{
		{"hotel_id", req.GetHotelId(), resp.GetHotelId()},
		{"start_date", req.GetStartDate(), resp.GetStartDate()},
		{"end_date", req.GetEndDate(), resp.GetEndDate()},
		{"party", req.GetParty(), resp.GetParty()},
	}); err != nil {
		return err
	}
	roomTypeCodes := make([]string, len(resp.GetRoomTypes()))
	ratePlanCodes := make([]string, len(resp.GetRatePlans()))
	for i, r := range resp.GetRoomTypes() {
		roomTypeCodes[i] = r.GetCode()
		err := checkRequired([]requiredTest{
			{fmt.Sprintf("room_types[%d] > code", i), r.GetCode()},
			{fmt.Sprintf("room_types[%d] > name", i), r.GetName().String()},
		})
		if err != nil {
			return err
		}
	}
	for i, r := range resp.GetRatePlans() {
		ratePlanCodes[i] = r.GetCode()
		err := checkRequired([]requiredTest{
			{fmt.Sprintf("rate_plans[%d] > code", i), r.GetCode()},
			{fmt.Sprintf("rate_plans[%d] > name", i), r.GetName().String()},
			{fmt.Sprintf("rate_plans[%d] > cancellation_policy", i), r.GetCancellationPolicy()},
		})
		if err != nil {
			return err
		}
	}
	for i, r := range resp.GetRoomRates() {
		rt := make([]requiredTest, len(r.GetLineItems()))
		for j, l := range r.GetLineItems() {
			rt[j] = requiredTest{fmt.Sprintf("room_rates[%d] > line_items[%d] > price", i, j), l.GetPrice().GetAmount()}
		}
		rt = append(rt, requiredTest{fmt.Sprintf("room_rates[%d] > code", i), r.GetCode()})
		if err := checkRequired(rt); err != nil {
			return err
		}
		if !valuePresent(r.GetRoomTypeCode(), roomTypeCodes) {
			return fmt.Errorf("room_rates > room_type_code %v not present in room_types > code", r.GetRoomTypeCode())
		}
		if !valuePresent(r.GetRatePlanCode(), ratePlanCodes) {
			return fmt.Errorf("room_rates > rate_plan_code %v not present in rate_plans > code", r.GetRatePlanCode())
		}
	}
	return nil
}
ValidateBookingAvailabilityResponse ensures the availability search criteria matches the echoed response.
package utils
import (
	"fmt"
	"log"
	"reflect"
	"regexp"
	"strings"
	"github.com/google/go-cmp/cmp"
	"github.com/golang/protobuf/proto"
	pb "github.com/google/hotel-booking-api-validator/v1"
)
const ISO3166 = `^A[^ABCHJKNPVY]|B[^CKPUX]|C[^BEJPQST]|D[EJKMOZ]|E[CEGHRST]|F[IJKM
	OR]|G[^CJKOVXZ]|H[KMNRTU]|I[DEL-OQ-T]|J[EMOP]|K[EGHIMNPRWYZ]|L[ABCIKR-VY]|M[^B
	IJ]|N[ACEFGILOPRUZ]|OM|P[AE-HKNRSTWY]|QA|R[EOSUW]|S[^FPQUW]|T[^ABEIPQSUXY]|U[A
	GMSYZ]|V[ACEGINU]|WF|WS|YE|YT|Z[AMW]$`
const DateFormat = `^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))$`
type validationTest struct {
	field string
	want  interface{}
	got   interface{}
}
type requiredTest struct {
	field string
	got   interface{}
}
type formatTest struct {
	field   string
	value   string
	pattern string
}
func compareFields(v []validationTest) error {
	var errorFields []string
	for _, vv := range v {
		if diff := cmp.Diff(vv.got, vv.want, cmp.Comparer(proto.Equal)); diff != "" {
			errorFields = append(errorFields, vv.field)
			log.Println(fmt.Errorf("%s did not match (-got +want)\n%s", vv.field, diff))
		}
	}
	if len(errorFields) > 0 {
		return fmt.Errorf("echo field(s) did not match request: %v", strings.Join(errorFields, ","))
	}
	return nil
}
func checkRequired(r []requiredTest) error {
	var errorFields []string
	for _, rr := range r {
		if reflect.ValueOf(rr.got).IsZero() {
			errorFields = append(errorFields, rr.field)
			log.Println(fmt.Errorf("Required field %s was not set", rr.field))
		}
	}
	if len(errorFields) > 0 {
		return fmt.Errorf("required field(s) missing: %v", strings.Join(errorFields, ", "))
	}
	return nil
}
func validateFormat(f []formatTest) error {
	var errorFields []string
	for _, ff := range f {
		matched, err := regexp.Match(ff.pattern, []byte(ff.value))
		if err != nil {
			return err
		}
		if !matched {
			errorFields = append(errorFields, ff.field)
			log.Println(fmt.Errorf("Field %s value %s did not match pattern %v", ff.field, ff.value, ff.pattern))
		}
	}
	if len(errorFields) > 0 {
		return fmt.Errorf("error validating format for field(s): %s", strings.Join(errorFields, ", "))
	}
	return nil
}
func valuePresent(v string, s []string) bool {
	for _, x := range s {
		if x == v {
			return true
		}
	}
	return false
}