Adding a new test client that supports the Order-based booking flow
diff --git a/README.md b/README.md index 2730405..f6454c1 100644 --- a/README.md +++ b/README.md
@@ -5,15 +5,15 @@ ## Testing Client -Before using the test utility, the Go programming language must be installed -on your workstation. A precompiled Go binary for your operating system can -be [found here](https://golang.org/dl/) +Before using either the order based or booking based test utilites, the Go +programming language must be installed on your workstation. A precompiled Go +binary for your operating system can be [found here](https://golang.org/dl/) This guide will assume you're using the default GOPATH and subsequent GOBIN. For a comprehensive explanation of the GOPATH env variable please see [this document](https://golang.org/dl/) by the Go team. -### Installing the utility with Linux +### Installing the utilities with Linux First, build your Go directory structure. A comprehensive guide on the intended structure of Go code can be [found here.](https://golang.org/doc/code.html) @@ -30,7 +30,7 @@ source ~/.bashrc -Next, retrieve the utility from the +Next, retrieve the utilities from the [maps-booking-v3 repository](https://maps-booking.googlesource.com/) git clone https://maps-booking.googlesource.com/maps-booking-v3 $HOME/go/src/github.com/maps-booking-v3/ @@ -39,9 +39,9 @@ cd $HOME/go go get -d ./... - go install $HOME/go/src/github.com/maps-booking-v3/testclient/main.go + go install $HOME/go/src/github.com/maps-booking-v3/testclient/<orderBasedTestClient.go or bookingBasedTestClient.go> -### Installing the utility with Windows Powershell +### Installing the utilities with Windows Powershell First, build your Go directory structure. A comprehensive guide on the intended structure of Go code can be [found here.](https://golang.org/doc/code.html) @@ -57,7 +57,7 @@ $env:GOPATH = (go env GOPATH) $env:GOBIN = (go env GOPATH) + "\bin" -Next, retrieve the utility from the +Next, retrieve the utilities from the [maps-booking-v3 repository](https://maps-booking.googlesource.com/) git clone https://maps-booking.googlesource.com/maps-booking-v3 $env:HOME\go\src\github.com\maps-booking-v3\ @@ -66,9 +66,9 @@ cd $env:HOME\go go get -d .\... - go install $env:HOME\go\src\github.com\maps-booking-v3\testclient\main.go + go install $env:HOME\go\src\github.com\maps-booking-v3\testclient\<orderBasedTestClient.go or bookingBasedTestClient.go> -### Using the utility +### Using the utilities After following the install steps above an executable should now live in @@ -81,6 +81,7 @@ All available flags can be displayed using the '--help' flag. The currently accepted flags are: + bookingBasedTestClient.go -all_tests Whether to test all endpoints. -availability_feed string @@ -121,6 +122,32 @@ -full_server_name string Fully qualified domain name. Same name used to sign CN. Only necessary if ca_file is specified and the base URL differs from the server address. + orderBasedTestClient.go + -all_tests + Whether to test all endpoints syncronously, including ListOrders flow. + -availability_feed string + Absolute path to availability feed required for all tests except health. Feeds can be in either json or pb3 format + -ca_file string + Absolute path to your server's Certificate Authority root cert. Downloading all roots currently recommended by the Google Internet Authority is a suitable alternative https://pki.google.com/roots.pem. Leave blank to connect using http rather than https. + -check_order_test + Whether to test the CheckOrderFulfillability endpoint. + -create_order_test + Whether to test the CreateOrder endpoint. + -credentials_file string + File containing credentials for your server. Leave blank to bypass authentication. File should have exactly one line of the form 'username:password'. + -full_server_name string + Fully qualified domain name. Same name used to sign CN. Only necessary if ca_file is specified and the base URL differs from the server address. + -health_check_test + Whether to test the Health endpoint. + -num_test_slots int + Maximum number of slots to test from availability_feed. Slots will be selected randomly (default 10) + -output_dir string + Absolute path of dir to dump log file. + -server_addr string + Your http server's address in the format of host:port (default "example.com:80") + -service_feed string + Absolute path to service feed required for all tests except health. Feeds can be in either json or pb3 format + Example Usage: bin/main -server_addr="localhost:443" -check_availability_test=true -output_dir="/tmp" -availability_feed="/tmp/test.json" @@ -131,12 +158,12 @@ It is important that as part of testing you verify all aspects of booking server invocation - authentication, availability, cancellation, and rescheduling tests. -As the final step, you must be able to execute the test utility with the flag --all_tests=true and it should successfully pass the entire series of tests. +As the final step, you must be able to execute the designated utility with the +flag -all_tests=true and it should successfully pass the entire series of tests. ### Parsing the output -The test utility will output a file with the prefix 'http_test_client_log_' +Both test utilities will output a file with the prefix 'http_test_client_log_' followed by a timestamp in RFC3339 format. The output file contains a complete log of all Requests and Responses sent/received by the testing tool as well as diffs of the expected response in the event of errors.
diff --git a/api/api.go b/api/api.go index f637ff7..ca2d365 100644 --- a/api/api.go +++ b/api/api.go
@@ -139,7 +139,7 @@ if err != nil { return fmt.Errorf("could not complete health check: %v", err) } - log.Printf("health check success!") + log.Println("health check success!") return nil } @@ -164,7 +164,7 @@ return bodyString, nil } -// CheckAvailability beforms a maps booking availability check on all supplied availability slots. This function +// CheckAvailability performs a maps booking availability check on all supplied availability slots. This function // will return all slots with a valid return. func CheckAvailability(a *fpb.Availability, conn *HTTPConnection) error { slot, err := utils.BuildSlotFrom(a) @@ -399,3 +399,169 @@ } return CancelBooking(resp.GetBooking(), conn) } + +// CheckOrderFulfillability attempts to send a CheckOrderFulfillabilityRequest +// to the connection endpoint and diff the results with what are expected. +func CheckOrderFulfillability(merchantID string, lineItems []*mpb.LineItem, conn *HTTPConnection) error { + reqPB := &mpb.CheckOrderFulfillabilityRequest{ + MerchantId: merchantID, + Item: lineItems, + } + req, err := conn.marshaler.MarshalToString(reqPB) + if err != nil { + return fmt.Errorf("Could not convert pb3 to json: %v", reqPB) + } + httpResp, err := sendRequest("CheckOrderFulfillability", req, conn) + if err != nil { + return fmt.Errorf("invalid response. CheckOrderFulfillability yielded error: %v", err) + } + var resp mpb.CheckOrderFulfillabilityResponse + if err := jsonpb.UnmarshalString(httpResp, &resp); err != nil { + return fmt.Errorf("CheckOrderFulfillability: Could not parse HTTP response to pb3: %v", err) + } + + // We ignore price for now. This is difficult to verify without knowing aggregator specific taxes/fees. + orderFulfillability := resp.GetFulfillability() + // TODO(ccawdrey): Add validation cases for other OrderFulFillability enums. + if diff := cmp.Diff(orderFulfillability.GetResult(), mpb.OrderFulfillability_CAN_FULFILL); diff != "" { + return fmt.Errorf("invalid response. CheckOrderFulfillability.Fulfillability.OrderFulfillabilityResult differ (-got +want)\n%s", diff) + } + if orderFulfillability.GetUnfulfillableReason() != "" { + return errors.New("invalid response. CheckOrderFulfillability.UnfulfillableReason should be empty") + } + + var respLineItems []*mpb.LineItem + for _, lineItemFulfillability := range orderFulfillability.GetItemFulfillability() { + if diff := cmp.Diff(lineItemFulfillability.GetResult(), mpb.LineItemFulfillability_CAN_FULFILL); diff != "" { + return fmt.Errorf("invalid response. CheckOrderFulfillability.Fulfillability.ItemFulfillability.Result for LineItem %v -- differ (-got +want)\n%s", lineItemFulfillability.GetItem(), diff) + } + if lineItemFulfillability.GetUnfulfillableReason() != "" { + return errors.New("invalid response. CheckOrderFulfillability.Fulfillability.ItemFulfillability.UnfulfillableReason should be empty") + } + + respLineItems = append(respLineItems, lineItemFulfillability.GetItem()) + } + + if err = utils.ValidateLineItems(respLineItems, lineItems, false); err != nil { + return fmt.Errorf("invalid response. CheckOrderFulfillability %v", err) + } + + return nil +} + +// CreateOrder will attempt to build an order from a merchant id and array of line orders. +func CreateOrder(merchantID string, lineItems []*mpb.LineItem, conn *HTTPConnection) (*mpb.Order, error) { + gen := rand.New(rand.NewSource(time.Now().UnixNano())) + + reqOrder := &mpb.Order{ + UserInformation: &mpb.UserInformation{ + UserId: userID, + GivenName: firstName, + FamilyName: lastName, + Telephone: telephone, + Email: email, + }, + PaymentInformation: &mpb.PaymentInformation{ + PrepaymentStatus: mpb.PrepaymentStatus_PREPAYMENT_NOT_PROVIDED, + }, + MerchantId: merchantID, + Item: lineItems, + } + reqPB := &mpb.CreateOrderRequest{ + Order: reqOrder, + IdempotencyToken: strconv.Itoa(gen.Intn(1000000)), + } + + req, err := conn.marshaler.MarshalToString(reqPB) + if err != nil { + return nil, fmt.Errorf("Could not convert pb3 to json: %v", reqPB) + } + httpResp, err := sendRequest("CreateOrder", req, conn) + if err != nil { + return nil, fmt.Errorf("invalid response. CreateOrder yielded error: %v", err) + } + var resp mpb.CreateOrderResponse + if err := jsonpb.UnmarshalString(httpResp, &resp); err != nil { + return nil, fmt.Errorf("CreateOrder: Could not parse HTTP response to pb3: %v", err) + } + + if resp.GetOrderFailure() != nil { + return nil, fmt.Errorf("invalid response. CreateOrder contains OrderFailure for request %v", reqPB) + } + + if err = utils.ValidateOrder(*resp.GetOrder(), *reqOrder); err != nil { + return nil, fmt.Errorf("invalid response. CreateOrder %v", err) + } + + // Perform idempotency test. + log.Printf("Idempotency check") + idemHTTPResp, err := sendRequest("CreateOrder", req, conn) + if err != nil { + return nil, fmt.Errorf("invalid response. Idempotency check yielded error: %v", err) + } + var idemResp mpb.CreateOrderResponse + if err := jsonpb.UnmarshalString(idemHTTPResp, &idemResp); err != nil { + return nil, fmt.Errorf("CreateOrder idem: Could not parse HTTP response to pb3: %v", err) + } + + if idemResp.GetOrderFailure() != nil { + return nil, errors.New("Idempotency check invalid. CreateOrder contains OrderFailure") + } + if err = utils.ValidateOrder(*idemResp.GetOrder(), *resp.GetOrder()); err != nil { + return nil, fmt.Errorf("Idempotency check invalid %v", err) + } + + return resp.GetOrder(), nil +} + +func sendListOrdersRequest(reqPB *mpb.ListOrdersRequest, conn *HTTPConnection) (mpb.ListOrdersResponse, error) { + req, err := conn.marshaler.MarshalToString(reqPB) + if err != nil { + return mpb.ListOrdersResponse{}, fmt.Errorf("Could not convert pb3 to json: %v", reqPB) + } + httpResp, err := sendRequest("ListOrders", req, conn) + if err != nil { + return mpb.ListOrdersResponse{}, fmt.Errorf("invalid response. ListOrders yielded error: %v", err) + } + var resp mpb.ListOrdersResponse + if err := jsonpb.UnmarshalString(httpResp, &resp); err != nil { + return resp, fmt.Errorf("ListOrders: Could not parse HTTP response to pb3: %v", err) + } + + return resp, nil +} + +// ListOrders first checks that the number and contents of the server's order state +// are consistent with what the test client assumes is present. +func ListOrders(orders []*mpb.Order, conn *HTTPConnection) error { + if len(orders) == 0 { + return errors.New("at least one order must be present for ListOrders to succeed") + } + + // UserId check first. + reqPB := &mpb.ListOrdersRequest{ + UserId: userID, + } + respUser, err := sendListOrdersRequest(reqPB, conn) + if err != nil { + return err + } + if err = utils.ValidateOrders(respUser.GetOrder(), orders); err != nil { + return fmt.Errorf("invalid response. ListOrders %v for request %v", err, reqPB) + } + + // Still here? OrderId check. + reqPB.Reset() + for _, order := range orders { + reqPB.OrderId = append(reqPB.OrderId, order.GetOrderId()) + } + respOrder, err := sendListOrdersRequest(reqPB, conn) + if err != nil { + return err + } + if err = utils.ValidateOrders(respOrder.GetOrder(), orders); err != nil { + return fmt.Errorf("invalid response. ListOrders %v for request %v", err, reqPB) + } + + return nil +}
diff --git a/feeds/availability_feed.pb.go b/feeds/availability_feed.pb.go deleted file mode 100644 index c41f821..0000000 --- a/feeds/availability_feed.pb.go +++ /dev/null
@@ -1,637 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: availability_feed.proto - -/* -Package maps_booking_feeds is a generated protocol buffer package. - -It is generated from these files: - availability_feed.proto - -It has these top-level messages: - FeedMetadata - AvailabilityFeed - ServiceAvailability - Availability - Resources - TimeRange -*/ -package maps_booking_feeds - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -type FeedMetadata_ProcessingInstruction int32 - -const ( - // By default we will assume that this feed is an incremental feed. - FeedMetadata_PROCESS_UNKNOWN FeedMetadata_ProcessingInstruction = 0 - // This Feed message is one shard of a complete feed. Anything previously - // supplied by this partner will be deleted; the contents of this feed - // represent the entire state of the world. - FeedMetadata_PROCESS_AS_COMPLETE FeedMetadata_ProcessingInstruction = 1 - // This Feed message is one shard of an incremental feed. Existing entities - // will be left untouched except as modified in this feed. - FeedMetadata_PROCESS_AS_INCREMENTAL FeedMetadata_ProcessingInstruction = 2 -) - -var FeedMetadata_ProcessingInstruction_name = map[int32]string{ - 0: "PROCESS_UNKNOWN", - 1: "PROCESS_AS_COMPLETE", - 2: "PROCESS_AS_INCREMENTAL", -} -var FeedMetadata_ProcessingInstruction_value = map[string]int32{ - "PROCESS_UNKNOWN": 0, - "PROCESS_AS_COMPLETE": 1, - "PROCESS_AS_INCREMENTAL": 2, -} - -func (x FeedMetadata_ProcessingInstruction) String() string { - return proto.EnumName(FeedMetadata_ProcessingInstruction_name, int32(x)) -} -func (FeedMetadata_ProcessingInstruction) EnumDescriptor() ([]byte, []int) { - return fileDescriptor0, []int{0, 0} -} - -type FeedMetadata struct { - // Instructs us how to process the feed: either as a shard of a complete feed, - // or as a shard of an incremental update. - ProcessingInstruction FeedMetadata_ProcessingInstruction `protobuf:"varint,1,opt,name=processing_instruction,json=processingInstruction,enum=maps.booking.feeds.FeedMetadata_ProcessingInstruction" json:"processing_instruction,omitempty"` - // The current shard and total number of shards for this feed. - // - // Shard number is assumed to be zero-based. - // - // There does not need to be any relationship to the file name. - // - // Shards do not need to be transferred in order, and they may not be - // processed in order. - ShardNumber int32 `protobuf:"varint,2,opt,name=shard_number,json=shardNumber" json:"shard_number,omitempty"` - TotalShards int32 `protobuf:"varint,3,opt,name=total_shards,json=totalShards" json:"total_shards,omitempty"` - // An identifier that must be consistent across all shards in a feed. - // This value must be globally unique across each feed type. - // - // This value ensures that complete feeds spanning multiple shards are - // processed together correctly. - // - // Clients only need to set this value when the processing_instruction is set - // to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by - // total_shards). - // - // Feeds that span multiple shards must set this nonce to the same value. - Nonce uint64 `protobuf:"varint,5,opt,name=nonce" json:"nonce,omitempty"` - // The timestamp at which this feed shard was generated. - // - // In Unix time format (seconds since the epoch). - GenerationTimestamp int64 `protobuf:"varint,4,opt,name=generation_timestamp,json=generationTimestamp" json:"generation_timestamp,omitempty"` -} - -func (m *FeedMetadata) Reset() { *m = FeedMetadata{} } -func (m *FeedMetadata) String() string { return proto.CompactTextString(m) } -func (*FeedMetadata) ProtoMessage() {} -func (*FeedMetadata) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -func (m *FeedMetadata) GetProcessingInstruction() FeedMetadata_ProcessingInstruction { - if m != nil { - return m.ProcessingInstruction - } - return FeedMetadata_PROCESS_UNKNOWN -} - -func (m *FeedMetadata) GetShardNumber() int32 { - if m != nil { - return m.ShardNumber - } - return 0 -} - -func (m *FeedMetadata) GetTotalShards() int32 { - if m != nil { - return m.TotalShards - } - return 0 -} - -func (m *FeedMetadata) GetNonce() uint64 { - if m != nil { - return m.Nonce - } - return 0 -} - -func (m *FeedMetadata) GetGenerationTimestamp() int64 { - if m != nil { - return m.GenerationTimestamp - } - return 0 -} - -type AvailabilityFeed struct { - Metadata *FeedMetadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"` - ServiceAvailability []*ServiceAvailability `protobuf:"bytes,2,rep,name=service_availability,json=serviceAvailability" json:"service_availability,omitempty"` -} - -func (m *AvailabilityFeed) Reset() { *m = AvailabilityFeed{} } -func (m *AvailabilityFeed) String() string { return proto.CompactTextString(m) } -func (*AvailabilityFeed) ProtoMessage() {} -func (*AvailabilityFeed) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -func (m *AvailabilityFeed) GetMetadata() *FeedMetadata { - if m != nil { - return m.Metadata - } - return nil -} - -func (m *AvailabilityFeed) GetServiceAvailability() []*ServiceAvailability { - if m != nil { - return m.ServiceAvailability - } - return nil -} - -type ServiceAvailability struct { - // If provided, we will consider the Availability entities provided to be a - // complete snapshot from [start_timestamp_restrict, end_timestamp_restrict). - // That is, all existing availability will be deleted if the following - // condition holds true: - // - // start_timestamp_restrict <= Availability.start_sec && - // Availability.start_sec < end_timestamp_restrict - // - // If a resource_restrict message is set, the condition is further restricted: - // - // Availability.resource.staff_id == resource_restrict.staff_id && - // Availability.resource.room_id == resource_restrict.room_id - // - // These fields are typically used to provide a complete update of - // availability in a given time range. - // - // Setting start_timestamp_restrict while leaving end_timestamp_restrict unset - // is interpreted to mean all time beginning at start_timestamp_restrict. - // - // Setting end_timestamp_restrict while leaving start_timestamp_restrict unset - // is interpreted to mean all time up to the end_timestamp_restrict. - // - // In Unix time format (seconds since the epoch). - StartTimestampRestrict int64 `protobuf:"varint,1,opt,name=start_timestamp_restrict,json=startTimestampRestrict" json:"start_timestamp_restrict,omitempty"` - EndTimestampRestrict int64 `protobuf:"varint,2,opt,name=end_timestamp_restrict,json=endTimestampRestrict" json:"end_timestamp_restrict,omitempty"` - // If provided, the timestamp restricts will be applied only to the given - // merchant or service. - // - // These fields are typically used to provide complete snapshot of - // availability in a given range (defined above) for a specific merchant or - // service. - // - // Leaving these fields unset, or setting these to the empty string or null, - // is interpreted to mean that no restrict is intended. - MerchantIdRestrict string `protobuf:"bytes,3,opt,name=merchant_id_restrict,json=merchantIdRestrict" json:"merchant_id_restrict,omitempty"` - ServiceIdRestrict string `protobuf:"bytes,4,opt,name=service_id_restrict,json=serviceIdRestrict" json:"service_id_restrict,omitempty"` - // Setting resources_restrict further restricts the scope of the update to - // just this set of resources. All id fields of the resources must match - // exactly. - ResourcesRestrict *Resources `protobuf:"bytes,6,opt,name=resources_restrict,json=resourcesRestrict" json:"resources_restrict,omitempty"` - Availability []*Availability `protobuf:"bytes,5,rep,name=availability" json:"availability,omitempty"` -} - -func (m *ServiceAvailability) Reset() { *m = ServiceAvailability{} } -func (m *ServiceAvailability) String() string { return proto.CompactTextString(m) } -func (*ServiceAvailability) ProtoMessage() {} -func (*ServiceAvailability) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - -func (m *ServiceAvailability) GetStartTimestampRestrict() int64 { - if m != nil { - return m.StartTimestampRestrict - } - return 0 -} - -func (m *ServiceAvailability) GetEndTimestampRestrict() int64 { - if m != nil { - return m.EndTimestampRestrict - } - return 0 -} - -func (m *ServiceAvailability) GetMerchantIdRestrict() string { - if m != nil { - return m.MerchantIdRestrict - } - return "" -} - -func (m *ServiceAvailability) GetServiceIdRestrict() string { - if m != nil { - return m.ServiceIdRestrict - } - return "" -} - -func (m *ServiceAvailability) GetResourcesRestrict() *Resources { - if m != nil { - return m.ResourcesRestrict - } - return nil -} - -func (m *ServiceAvailability) GetAvailability() []*Availability { - if m != nil { - return m.Availability - } - return nil -} - -// An availability of the merchant's service, indicating time and number -// of spots. -// The availability feed should be a list of this message. -// Please note that it's up to the partner to call out all the possible -// availabilities. -// If a massage therapist is available 9am-12pm, and they provide -// one-hour massage sessions, the aggregator should provide the feed as -// availability {start_sec: 9am, duration: 60 minutes, ...} -// availability {start_sec: 10am, duration: 60 minutes, ...} -// availability {start_sec: 11am, duration: 60 minutes, ...} -// instead of -// availability {start_sec: 9am, duration: 180 minutes, ...} -// -type Availability struct { - // An opaque string from an aggregator to identify a merchant. - MerchantId string `protobuf:"bytes,1,opt,name=merchant_id,json=merchantId" json:"merchant_id,omitempty"` - // An opaque string from aggregator to identify a service of the - // merchant. - ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId" json:"service_id,omitempty"` - // Start time of this availability, using epoch time in seconds. - StartSec int64 `protobuf:"varint,3,opt,name=start_sec,json=startSec" json:"start_sec,omitempty"` - // Duration of the service in seconds, e.g. 30 minutes for a chair massage. - DurationSec int64 `protobuf:"varint,4,opt,name=duration_sec,json=durationSec" json:"duration_sec,omitempty"` - // Number of total spots and open spots of this availability. - // E.g. a Yoga class of 10 spots with 3 booked. - // availability {spots_total: 10, spots_open: 7 ...} - // E.g. a chair massage session which was already booked. - // availability {spots_total: 1, spots_open: 0 ...} - // - // Note: If sending requests using the availability compression format defined - // below, these two fields will be inferred. A Recurrence - // implies spots_total=1 and spots_open=1. A ScheduleException implies - // spots_total=1 and spots_open=0. - SpotsTotal int64 `protobuf:"varint,5,opt,name=spots_total,json=spotsTotal" json:"spots_total,omitempty"` - SpotsOpen int64 `protobuf:"varint,6,opt,name=spots_open,json=spotsOpen" json:"spots_open,omitempty"` - // An optional opaque string to identify this availability slot. If set, it - // will be included in the requests that book/update/cancel appointments. - AvailabilityTag string `protobuf:"bytes,7,opt,name=availability_tag,json=availabilityTag" json:"availability_tag,omitempty"` - // Optional resources used to disambiguate this availability slot from - // others when different staff, room, or party_size values are part - // of the service. - // - // E.g. the same Yoga class with two 2 instructors. - // availability { resources { staff_id: "1" staff_name: "Amy" } - // spots_total: 10 spots_open: 7 } - // availability { resources { staff_id: "2" staff_name: "John" } - // spots_total: 5 spots_open: 2 } - Resources *Resources `protobuf:"bytes,8,opt,name=resources" json:"resources,omitempty"` - // A list of ids referencing the payment options which can be used to pay - // for this slot. The actual payment options are defined at the Merchant - // level, and can also be shared among multiple Merchants. - // - // This field overrides any payment_option_ids specified in the service - // message. Similarly payment_option_ids specified here do NOT have to be - // present in the service message, though must be defined at the - // Merchant level. - PaymentOptionId []string `protobuf:"bytes,9,rep,name=payment_option_id,json=paymentOptionId" json:"payment_option_id,omitempty"` - // The recurrence information for the availability, representing more than one - // start time. A recurrence should contain appointments for one working day. - Recurrence *Availability_Recurrence `protobuf:"bytes,10,opt,name=recurrence" json:"recurrence,omitempty"` - // When this service cannot be scheduled. To limit the number of - // schedule_exception messages consider joining adjacent exceptions. - ScheduleException []*Availability_ScheduleException `protobuf:"bytes,11,rep,name=schedule_exception,json=scheduleException" json:"schedule_exception,omitempty"` -} - -func (m *Availability) Reset() { *m = Availability{} } -func (m *Availability) String() string { return proto.CompactTextString(m) } -func (*Availability) ProtoMessage() {} -func (*Availability) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } - -func (m *Availability) GetMerchantId() string { - if m != nil { - return m.MerchantId - } - return "" -} - -func (m *Availability) GetServiceId() string { - if m != nil { - return m.ServiceId - } - return "" -} - -func (m *Availability) GetStartSec() int64 { - if m != nil { - return m.StartSec - } - return 0 -} - -func (m *Availability) GetDurationSec() int64 { - if m != nil { - return m.DurationSec - } - return 0 -} - -func (m *Availability) GetSpotsTotal() int64 { - if m != nil { - return m.SpotsTotal - } - return 0 -} - -func (m *Availability) GetSpotsOpen() int64 { - if m != nil { - return m.SpotsOpen - } - return 0 -} - -func (m *Availability) GetAvailabilityTag() string { - if m != nil { - return m.AvailabilityTag - } - return "" -} - -func (m *Availability) GetResources() *Resources { - if m != nil { - return m.Resources - } - return nil -} - -func (m *Availability) GetPaymentOptionId() []string { - if m != nil { - return m.PaymentOptionId - } - return nil -} - -func (m *Availability) GetRecurrence() *Availability_Recurrence { - if m != nil { - return m.Recurrence - } - return nil -} - -func (m *Availability) GetScheduleException() []*Availability_ScheduleException { - if m != nil { - return m.ScheduleException - } - return nil -} - -// Recurrence messages are optional, but allow for a more compact -// representation of consistently repeating availability slots. They typically -// represent a day's working schedule. -// ScheduleException messages are then used to represent booked/unavailable -// time ranges within the work day. -// -// Requirements: -// 1. The expansion of availability slots or recurrences must NOT create -// identical slots. If the ids, start_sec, duration_sec, and resources -// match, slots are considered identical. -// 2. Do NOT mix the standard availability format and recurrence within the -// slots of a single service. Recurrence benefits merchants/services that -// offer appointments. The standard format is geared towards -// merchants/services with regularly scheduled classes. -type Availability_Recurrence struct { - // The inclusive maximum UTC timestamp the availability repeats until. - RepeatUntilSec int64 `protobuf:"varint,1,opt,name=repeat_until_sec,json=repeatUntilSec" json:"repeat_until_sec,omitempty"` - // Defines the time between successive availability slots. - // - // E.g. An availability with a duration of 20 min, a repeat_every_sec of - // 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will - // yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am, - // 11-11:20am. - RepeatEverySec int32 `protobuf:"varint,2,opt,name=repeat_every_sec,json=repeatEverySec" json:"repeat_every_sec,omitempty"` -} - -func (m *Availability_Recurrence) Reset() { *m = Availability_Recurrence{} } -func (m *Availability_Recurrence) String() string { return proto.CompactTextString(m) } -func (*Availability_Recurrence) ProtoMessage() {} -func (*Availability_Recurrence) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3, 0} } - -func (m *Availability_Recurrence) GetRepeatUntilSec() int64 { - if m != nil { - return m.RepeatUntilSec - } - return 0 -} - -func (m *Availability_Recurrence) GetRepeatEverySec() int32 { - if m != nil { - return m.RepeatEverySec - } - return 0 -} - -// ScheduleException messages are used to represent booked/unavailable time -// ranges within the work day. As time slots are booked, the list of -// exceptions should grow to reflect the newly unavailable time ranges. -// The recurrence itself shouldn't be modified. -type Availability_ScheduleException struct { - // The time range of the exception. - TimeRange *TimeRange `protobuf:"bytes,1,opt,name=time_range,json=timeRange" json:"time_range,omitempty"` -} - -func (m *Availability_ScheduleException) Reset() { *m = Availability_ScheduleException{} } -func (m *Availability_ScheduleException) String() string { return proto.CompactTextString(m) } -func (*Availability_ScheduleException) ProtoMessage() {} -func (*Availability_ScheduleException) Descriptor() ([]byte, []int) { - return fileDescriptor0, []int{3, 1} -} - -func (m *Availability_ScheduleException) GetTimeRange() *TimeRange { - if m != nil { - return m.TimeRange - } - return nil -} - -// A resource is used to disambiguate availability slots from one another when -// different staff, room or party_size values are part of the service. -// Multiple slots for the same service and time interval can co-exist when they -// have different resources. -type Resources struct { - // Optional id for a staff member providing the service. This field identifies - // the staff member across all merchants, services, and availability records. - // It also needs to be stable over time to allow correlation with past - // bookings. - // This field must be present if staff_name is present. - StaffId string `protobuf:"bytes,1,opt,name=staff_id,json=staffId" json:"staff_id,omitempty"` - // Optional name of a staff member providing the service. This field will be - // displayed to users making a booking, and should be human readable, as - // opposed to an opaque identifier. - // This field must be present if staff_id is present. - StaffName string `protobuf:"bytes,2,opt,name=staff_name,json=staffName" json:"staff_name,omitempty"` - // An optional id for the room the service is located in. This field - // identifies the room across all merchants, services, and availability - // records. It also needs to be stable over time to allow correlation with - // past bookings. - // This field must be present if room_name is present. - RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId" json:"room_id,omitempty"` - // An optional name for the room the service is located in. This - // field will be displayed to users making a booking, and should be human - // readable, as opposed to an opaque identifier. - // This field must be present if room_id is present. - RoomName string `protobuf:"bytes,4,opt,name=room_name,json=roomName" json:"room_name,omitempty"` - // Applicable only for Dining: The party size which can be accommodated - // during this time slot. A restaurant can be associated with multiple Slots - // for the same time, each specifying a different party_size, if for instance - // 2, 3, or 4 people can be seated with a reservation. - PartySize int32 `protobuf:"varint,5,opt,name=party_size,json=partySize" json:"party_size,omitempty"` -} - -func (m *Resources) Reset() { *m = Resources{} } -func (m *Resources) String() string { return proto.CompactTextString(m) } -func (*Resources) ProtoMessage() {} -func (*Resources) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } - -func (m *Resources) GetStaffId() string { - if m != nil { - return m.StaffId - } - return "" -} - -func (m *Resources) GetStaffName() string { - if m != nil { - return m.StaffName - } - return "" -} - -func (m *Resources) GetRoomId() string { - if m != nil { - return m.RoomId - } - return "" -} - -func (m *Resources) GetRoomName() string { - if m != nil { - return m.RoomName - } - return "" -} - -func (m *Resources) GetPartySize() int32 { - if m != nil { - return m.PartySize - } - return 0 -} - -type TimeRange struct { - BeginSec int64 `protobuf:"varint,1,opt,name=begin_sec,json=beginSec" json:"begin_sec,omitempty"` - EndSec int64 `protobuf:"varint,2,opt,name=end_sec,json=endSec" json:"end_sec,omitempty"` -} - -func (m *TimeRange) Reset() { *m = TimeRange{} } -func (m *TimeRange) String() string { return proto.CompactTextString(m) } -func (*TimeRange) ProtoMessage() {} -func (*TimeRange) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } - -func (m *TimeRange) GetBeginSec() int64 { - if m != nil { - return m.BeginSec - } - return 0 -} - -func (m *TimeRange) GetEndSec() int64 { - if m != nil { - return m.EndSec - } - return 0 -} - -func init() { - proto.RegisterType((*FeedMetadata)(nil), "maps.booking.feeds.FeedMetadata") - proto.RegisterType((*AvailabilityFeed)(nil), "maps.booking.feeds.AvailabilityFeed") - proto.RegisterType((*ServiceAvailability)(nil), "maps.booking.feeds.ServiceAvailability") - proto.RegisterType((*Availability)(nil), "maps.booking.feeds.Availability") - proto.RegisterType((*Availability_Recurrence)(nil), "maps.booking.feeds.Availability.Recurrence") - proto.RegisterType((*Availability_ScheduleException)(nil), "maps.booking.feeds.Availability.ScheduleException") - proto.RegisterType((*Resources)(nil), "maps.booking.feeds.Resources") - proto.RegisterType((*TimeRange)(nil), "maps.booking.feeds.TimeRange") - proto.RegisterEnum("maps.booking.feeds.FeedMetadata_ProcessingInstruction", FeedMetadata_ProcessingInstruction_name, FeedMetadata_ProcessingInstruction_value) -} - -func init() { proto.RegisterFile("availability_feed.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 859 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xdb, 0x6e, 0x23, 0x45, - 0x10, 0xc5, 0x76, 0x9c, 0x64, 0xca, 0xd1, 0xae, 0xdd, 0xf6, 0x26, 0x43, 0x50, 0xb4, 0xc6, 0x2f, - 0x18, 0x90, 0x2c, 0x08, 0x08, 0x21, 0xb1, 0x2f, 0x51, 0x30, 0x52, 0xb4, 0x89, 0x1d, 0x7a, 0xbc, - 0x42, 0xe2, 0x65, 0x68, 0xcf, 0x54, 0x9c, 0x11, 0x9e, 0x9e, 0x51, 0x77, 0x3b, 0x22, 0xfb, 0x29, - 0x7c, 0x03, 0xff, 0xc1, 0xe7, 0xf0, 0x01, 0xbc, 0xa0, 0xae, 0xb9, 0x9a, 0x58, 0xca, 0xbe, 0x79, - 0x4e, 0x9d, 0xd3, 0x97, 0x73, 0xaa, 0xda, 0x70, 0x22, 0x1e, 0x44, 0xb4, 0x16, 0xcb, 0x68, 0x1d, - 0x99, 0x47, 0xff, 0x0e, 0x31, 0x9c, 0xa4, 0x2a, 0x31, 0x09, 0x63, 0xb1, 0x48, 0xf5, 0x64, 0x99, - 0x24, 0xbf, 0x47, 0x72, 0x35, 0xb1, 0x05, 0x3d, 0xfa, 0xa7, 0x09, 0x47, 0x3f, 0x21, 0x86, 0x37, - 0x68, 0x44, 0x28, 0x8c, 0x60, 0x31, 0x1c, 0xa7, 0x2a, 0x09, 0x50, 0xeb, 0x48, 0xae, 0xfc, 0x48, - 0x6a, 0xa3, 0x36, 0x81, 0x89, 0x12, 0xe9, 0x36, 0x86, 0x8d, 0xf1, 0x8b, 0xf3, 0xef, 0x26, 0x4f, - 0x57, 0x99, 0xd4, 0x57, 0x98, 0xdc, 0x96, 0xf2, 0xab, 0x4a, 0xcd, 0x5f, 0xa5, 0xbb, 0x60, 0xf6, - 0x29, 0x1c, 0xe9, 0x7b, 0xa1, 0x42, 0x5f, 0x6e, 0xe2, 0x25, 0x2a, 0xb7, 0x39, 0x6c, 0x8c, 0xdb, - 0xbc, 0x43, 0xd8, 0x8c, 0x20, 0x4b, 0x31, 0x89, 0x11, 0x6b, 0x9f, 0x40, 0xed, 0xb6, 0x32, 0x0a, - 0x61, 0x1e, 0x41, 0x6c, 0x00, 0x6d, 0x99, 0xc8, 0x00, 0xdd, 0xf6, 0xb0, 0x31, 0xde, 0xe3, 0xd9, - 0x07, 0xfb, 0x1a, 0x06, 0x2b, 0x94, 0xa8, 0x84, 0xdd, 0xc9, 0x37, 0x51, 0x8c, 0xda, 0x88, 0x38, - 0x75, 0xf7, 0x86, 0x8d, 0x71, 0x8b, 0xf7, 0xab, 0xda, 0xa2, 0x28, 0x8d, 0x04, 0xbc, 0xda, 0x79, - 0x7c, 0xd6, 0x87, 0x97, 0xb7, 0x7c, 0x7e, 0x39, 0xf5, 0x3c, 0xff, 0xdd, 0xec, 0xed, 0x6c, 0xfe, - 0xcb, 0xac, 0xfb, 0x11, 0x3b, 0x81, 0x7e, 0x01, 0x5e, 0x78, 0xfe, 0xe5, 0xfc, 0xe6, 0xf6, 0x7a, - 0xba, 0x98, 0x76, 0x1b, 0xec, 0x14, 0x8e, 0x6b, 0x85, 0xab, 0xd9, 0x25, 0x9f, 0xde, 0x4c, 0x67, - 0x8b, 0x8b, 0xeb, 0x6e, 0x73, 0xf4, 0x57, 0x03, 0xba, 0x17, 0xb5, 0x84, 0xac, 0x77, 0xec, 0x0d, - 0x1c, 0xc6, 0xb9, 0x7f, 0xe4, 0x73, 0xe7, 0x7c, 0xf8, 0x9c, 0xcf, 0xbc, 0x54, 0xb0, 0x5f, 0x61, - 0xa0, 0x51, 0x3d, 0x44, 0x01, 0xfa, 0xf5, 0xec, 0xdd, 0xe6, 0xb0, 0x35, 0xee, 0x9c, 0x7f, 0xb6, - 0x6b, 0x25, 0x2f, 0xe3, 0xd7, 0x0f, 0xc2, 0xfb, 0xfa, 0x29, 0x38, 0xfa, 0xb7, 0x09, 0xfd, 0x1d, - 0x64, 0xf6, 0x3d, 0xb8, 0xda, 0x08, 0x65, 0x2a, 0x5f, 0x7d, 0x85, 0xda, 0xa8, 0x28, 0x30, 0x74, - 0x83, 0x16, 0x3f, 0xa6, 0x7a, 0xe9, 0x2d, 0xcf, 0xab, 0xec, 0x5b, 0x38, 0x46, 0x19, 0xee, 0xd2, - 0x35, 0x49, 0x37, 0x40, 0x19, 0x3e, 0x55, 0x7d, 0x05, 0x83, 0x18, 0x55, 0x70, 0x2f, 0xa4, 0xf1, - 0xa3, 0xb0, 0xd2, 0xd8, 0x6e, 0x70, 0x38, 0x2b, 0x6a, 0x57, 0x61, 0xa9, 0x98, 0x40, 0x71, 0xa1, - 0x2d, 0xc1, 0x1e, 0x09, 0x7a, 0x79, 0xa9, 0xc6, 0xbf, 0x06, 0xa6, 0x50, 0x27, 0x1b, 0x15, 0xa0, - 0xae, 0xe8, 0xfb, 0x94, 0xc6, 0xd9, 0x2e, 0x0f, 0x79, 0xc1, 0xe6, 0xbd, 0x52, 0x58, 0xae, 0xf6, - 0x23, 0x1c, 0x6d, 0x65, 0xd1, 0xa6, 0x2c, 0x76, 0xa6, 0xba, 0x15, 0xc2, 0x96, 0x6a, 0xf4, 0x77, - 0x1b, 0x8e, 0xb6, 0x6c, 0x7f, 0x0d, 0x9d, 0x9a, 0x0d, 0xe4, 0xb4, 0xc3, 0xa1, 0xba, 0x3d, 0x3b, - 0x03, 0xa8, 0x6e, 0x4d, 0x8e, 0x3a, 0xdc, 0x29, 0x2f, 0xcb, 0x3e, 0x01, 0x27, 0x8b, 0x4d, 0x63, - 0x40, 0xde, 0xb5, 0xf8, 0x21, 0x01, 0x1e, 0x06, 0x76, 0xd2, 0xc2, 0x4d, 0x3e, 0x2e, 0xb6, 0x9e, - 0x0d, 0x4a, 0xa7, 0xc0, 0x2c, 0xe5, 0x35, 0x74, 0x74, 0x9a, 0x18, 0xed, 0xd3, 0xf8, 0xd1, 0xbc, - 0xb5, 0x38, 0x10, 0xb4, 0xb0, 0x08, 0xed, 0x4f, 0x84, 0x24, 0x45, 0x49, 0xee, 0xb5, 0xb8, 0x43, - 0xc8, 0x3c, 0x45, 0xc9, 0x3e, 0x87, 0xee, 0xd6, 0xf3, 0x64, 0xc4, 0xca, 0x3d, 0xa0, 0x43, 0xbe, - 0xac, 0xe3, 0x0b, 0xb1, 0x62, 0x3f, 0x80, 0x53, 0xda, 0xea, 0x1e, 0x7e, 0x48, 0x0c, 0x15, 0x9f, - 0x7d, 0x01, 0xbd, 0x54, 0x3c, 0xc6, 0x28, 0x8d, 0x9f, 0xa4, 0x74, 0xa1, 0x28, 0x74, 0x9d, 0x61, - 0xcb, 0x6e, 0x94, 0x17, 0xe6, 0x84, 0x5f, 0x85, 0xec, 0x2d, 0x80, 0xc2, 0x60, 0xa3, 0x14, 0xda, - 0x27, 0x04, 0x68, 0xa7, 0x2f, 0x9f, 0x0b, 0x6a, 0xc2, 0x4b, 0x09, 0xaf, 0xc9, 0x99, 0x00, 0xa6, - 0x83, 0x7b, 0x0c, 0x37, 0x6b, 0xf4, 0xf1, 0x8f, 0x00, 0x69, 0x13, 0xb7, 0x43, 0xe9, 0x9f, 0x3f, - 0xbb, 0xa8, 0x97, 0x4b, 0xa7, 0x85, 0x92, 0xf7, 0xf4, 0xff, 0xa1, 0xd3, 0xdf, 0x00, 0xaa, 0xcd, - 0xd9, 0x18, 0xba, 0x0a, 0x53, 0x14, 0xc6, 0xdf, 0x48, 0x13, 0xad, 0x29, 0xb8, 0x6c, 0x00, 0x5f, - 0x64, 0xf8, 0x3b, 0x0b, 0xdb, 0xec, 0x2a, 0x26, 0x3e, 0xa0, 0x7a, 0x24, 0x66, 0xf6, 0xde, 0xe6, - 0xcc, 0xa9, 0x85, 0x3d, 0x0c, 0x4e, 0x7f, 0x86, 0xde, 0x93, 0x93, 0xb0, 0x37, 0x00, 0x76, 0x66, - 0x7d, 0x25, 0xe4, 0x0a, 0xf3, 0x57, 0x6a, 0x67, 0x20, 0x76, 0x78, 0xb9, 0x25, 0x71, 0xc7, 0x14, - 0x3f, 0x47, 0x7f, 0x36, 0xc0, 0x29, 0x93, 0x62, 0x1f, 0x83, 0xed, 0xba, 0xbb, 0xbb, 0xaa, 0x87, - 0x0f, 0xe8, 0x3b, 0x6f, 0x60, 0x2a, 0x49, 0x11, 0x63, 0xd9, 0xc0, 0x16, 0x99, 0x89, 0x18, 0xd9, - 0x09, 0x1c, 0xa8, 0x24, 0x89, 0xad, 0x30, 0x1b, 0xfd, 0x7d, 0xfb, 0x99, 0x75, 0x36, 0x15, 0x48, - 0x96, 0x0d, 0xf9, 0xa1, 0x05, 0x48, 0x75, 0x06, 0x90, 0x0a, 0x65, 0x1e, 0x7d, 0x1d, 0xbd, 0xcf, - 0xfe, 0x25, 0xda, 0xdc, 0x21, 0xc4, 0x8b, 0xde, 0xe3, 0xe8, 0x02, 0x9c, 0xf2, 0xd0, 0x76, 0xa1, - 0x25, 0xae, 0x22, 0x59, 0x73, 0xf2, 0x90, 0x00, 0xeb, 0xe1, 0x09, 0x1c, 0xd8, 0xc7, 0xab, 0xb0, - 0xae, 0xc5, 0xf7, 0x51, 0x86, 0x1e, 0x06, 0xcb, 0x7d, 0xfa, 0x8f, 0xfd, 0xe6, 0xbf, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x08, 0xf3, 0x9e, 0x1d, 0x7e, 0x07, 0x00, 0x00, -}
diff --git a/feeds/availability_feed.proto b/feeds/availability_feed.proto deleted file mode 100644 index e25bd0e..0000000 --- a/feeds/availability_feed.proto +++ /dev/null
@@ -1,249 +0,0 @@ -syntax = "proto3"; - -package maps.booking.feeds; - -message FeedMetadata { - enum ProcessingInstruction { - // By default we will assume that this feed is an incremental feed. - PROCESS_UNKNOWN = 0; - - // This Feed message is one shard of a complete feed. Anything previously - // supplied by this partner will be deleted; the contents of this feed - // represent the entire state of the world. - PROCESS_AS_COMPLETE = 1; - - // This Feed message is one shard of an incremental feed. Existing entities - // will be left untouched except as modified in this feed. - PROCESS_AS_INCREMENTAL = 2; - } - - // Instructs us how to process the feed: either as a shard of a complete feed, - // or as a shard of an incremental update. - ProcessingInstruction processing_instruction = 1; - - // The current shard and total number of shards for this feed. - // - // Shard number is assumed to be zero-based. - // - // There does not need to be any relationship to the file name. - // - // Shards do not need to be transferred in order, and they may not be - // processed in order. - int32 shard_number = 2; - int32 total_shards = 3; - - // An identifier that must be consistent across all shards in a feed. - // This value must be globally unique across each feed type. - // - // This value ensures that complete feeds spanning multiple shards are - // processed together correctly. - // - // Clients only need to set this value when the processing_instruction is set - // to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by - // total_shards). - // - // Feeds that span multiple shards must set this nonce to the same value. - uint64 nonce = 5; - - // The timestamp at which this feed shard was generated. - // - // In Unix time format (seconds since the epoch). - int64 generation_timestamp = 4; -} - -message AvailabilityFeed { - FeedMetadata metadata = 1; - repeated ServiceAvailability service_availability = 2; -} - -message ServiceAvailability { - // If provided, we will consider the Availability entities provided to be a - // complete snapshot from [start_timestamp_restrict, end_timestamp_restrict). - // That is, all existing availability will be deleted if the following - // condition holds true: - // - // start_timestamp_restrict <= Availability.start_sec && - // Availability.start_sec < end_timestamp_restrict - // - // If a resource_restrict message is set, the condition is further restricted: - // - // Availability.resource.staff_id == resource_restrict.staff_id && - // Availability.resource.room_id == resource_restrict.room_id - // - // These fields are typically used to provide a complete update of - // availability in a given time range. - // - // Setting start_timestamp_restrict while leaving end_timestamp_restrict unset - // is interpreted to mean all time beginning at start_timestamp_restrict. - // - // Setting end_timestamp_restrict while leaving start_timestamp_restrict unset - // is interpreted to mean all time up to the end_timestamp_restrict. - // - // In Unix time format (seconds since the epoch). - int64 start_timestamp_restrict = 1; - int64 end_timestamp_restrict = 2; - - // If provided, the timestamp restricts will be applied only to the given - // merchant or service. - // - // These fields are typically used to provide complete snapshot of - // availability in a given range (defined above) for a specific merchant or - // service. - // - // Leaving these fields unset, or setting these to the empty string or null, - // is interpreted to mean that no restrict is intended. - string merchant_id_restrict = 3; - string service_id_restrict = 4; - - // Setting resources_restrict further restricts the scope of the update to - // just this set of resources. All id fields of the resources must match - // exactly. - Resources resources_restrict = 6; - - repeated Availability availability = 5; -} - -// An availability of the merchant's service, indicating time and number -// of spots. -// The availability feed should be a list of this message. -// Please note that it's up to the partner to call out all the possible -// availabilities. -// If a massage therapist is available 9am-12pm, and they provide -// one-hour massage sessions, the aggregator should provide the feed as -// availability {start_sec: 9am, duration: 60 minutes, ...} -// availability {start_sec: 10am, duration: 60 minutes, ...} -// availability {start_sec: 11am, duration: 60 minutes, ...} -// instead of -// availability {start_sec: 9am, duration: 180 minutes, ...} -// -message Availability { - // An opaque string from an aggregator to identify a merchant. - string merchant_id = 1; - // An opaque string from aggregator to identify a service of the - // merchant. - string service_id = 2; - // Start time of this availability, using epoch time in seconds. - int64 start_sec = 3; - // Duration of the service in seconds, e.g. 30 minutes for a chair massage. - int64 duration_sec = 4; - // Number of total spots and open spots of this availability. - // E.g. a Yoga class of 10 spots with 3 booked. - // availability {spots_total: 10, spots_open: 7 ...} - // E.g. a chair massage session which was already booked. - // availability {spots_total: 1, spots_open: 0 ...} - // - // Note: If sending requests using the availability compression format defined - // below, these two fields will be inferred. A Recurrence - // implies spots_total=1 and spots_open=1. A ScheduleException implies - // spots_total=1 and spots_open=0. - int64 spots_total = 5; - int64 spots_open = 6; - // An optional opaque string to identify this availability slot. If set, it - // will be included in the requests that book/update/cancel appointments. - string availability_tag = 7; - - // Optional resources used to disambiguate this availability slot from - // others when different staff, room, or party_size values are part - // of the service. - // - // E.g. the same Yoga class with two 2 instructors. - // availability { resources { staff_id: "1" staff_name: "Amy" } - // spots_total: 10 spots_open: 7 } - // availability { resources { staff_id: "2" staff_name: "John" } - // spots_total: 5 spots_open: 2 } - Resources resources = 8; - - // A list of ids referencing the payment options which can be used to pay - // for this slot. The actual payment options are defined at the Merchant - // level, and can also be shared among multiple Merchants. - // - // This field overrides any payment_option_ids specified in the service - // message. Similarly payment_option_ids specified here do NOT have to be - // present in the service message, though must be defined at the - // Merchant level. - repeated string payment_option_id = 9; - - // Recurrence messages are optional, but allow for a more compact - // representation of consistently repeating availability slots. They typically - // represent a day's working schedule. - // ScheduleException messages are then used to represent booked/unavailable - // time ranges within the work day. - // - // Requirements: - // 1. The expansion of availability slots or recurrences must NOT create - // identical slots. If the ids, start_sec, duration_sec, and resources - // match, slots are considered identical. - // 2. Do NOT mix the standard availability format and recurrence within the - // slots of a single service. Recurrence benefits merchants/services that - // offer appointments. The standard format is geared towards - // merchants/services with regularly scheduled classes. - message Recurrence { - // The inclusive maximum UTC timestamp the availability repeats until. - int64 repeat_until_sec = 1; - // Defines the time between successive availability slots. - // - // E.g. An availability with a duration of 20 min, a repeat_every_sec of - // 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will - // yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am, - // 11-11:20am. - int32 repeat_every_sec = 2; - } - // The recurrence information for the availability, representing more than one - // start time. A recurrence should contain appointments for one working day. - Recurrence recurrence = 10; - - // ScheduleException messages are used to represent booked/unavailable time - // ranges within the work day. As time slots are booked, the list of - // exceptions should grow to reflect the newly unavailable time ranges. - // The recurrence itself shouldn't be modified. - message ScheduleException { - // The time range of the exception. - TimeRange time_range = 1; - } - // When this service cannot be scheduled. To limit the number of - // schedule_exception messages consider joining adjacent exceptions. - repeated ScheduleException schedule_exception = 11; -} - -// A resource is used to disambiguate availability slots from one another when -// different staff, room or party_size values are part of the service. -// Multiple slots for the same service and time interval can co-exist when they -// have different resources. -message Resources { - // Optional id for a staff member providing the service. This field identifies - // the staff member across all merchants, services, and availability records. - // It also needs to be stable over time to allow correlation with past - // bookings. - // This field must be present if staff_name is present. - string staff_id = 1; - - // Optional name of a staff member providing the service. This field will be - // displayed to users making a booking, and should be human readable, as - // opposed to an opaque identifier. - // This field must be present if staff_id is present. - string staff_name = 2; - - // An optional id for the room the service is located in. This field - // identifies the room across all merchants, services, and availability - // records. It also needs to be stable over time to allow correlation with - // past bookings. - // This field must be present if room_name is present. - string room_id = 3; - - // An optional name for the room the service is located in. This - // field will be displayed to users making a booking, and should be human - // readable, as opposed to an opaque identifier. - // This field must be present if room_id is present. - string room_name = 4; - - // Applicable only for Dining: The party size which can be accommodated - // during this time slot. A restaurant can be associated with multiple Slots - // for the same time, each specifying a different party_size, if for instance - // 2, 3, or 4 people can be seated with a reservation. - int32 party_size = 5; -} - -message TimeRange { - int64 begin_sec = 1; - int64 end_sec = 2; -} \ No newline at end of file
diff --git a/feeds/feeds.pb.go b/feeds/feeds.pb.go new file mode 100644 index 0000000..9902bb9 --- /dev/null +++ b/feeds/feeds.pb.go
@@ -0,0 +1,2181 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: feeds.proto + +/* +Package maps_booking_feeds is a generated protocol buffer package. + +It is generated from these files: + feeds.proto + +It has these top-level messages: + FeedMetadata + AvailabilityFeed + ServiceAvailability + Availability + Resources + TimeRange + ServiceFeed + Service + Price + SchedulingRules + TaxRate + ServiceIntakeFormField + ServiceIntakeForm + Deposit + NoShowFee + PriceType + RequireCreditCard + ActionLink + ActionPlatform + TicketType + RelatedMedia + ServiceAttributeValueId +*/ + +package maps_booking_feeds + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Defines how a total price is determined from an availability. +type PriceType int32 + +const ( + // The price is for a fixed amount. This is the default value if the field is + // not set. + PriceType_FIXED_RATE_DEFAULT PriceType = 0 + // The price specified is per person, and the total price is calculated + // according to the party size specified in Resources as + // price_micros * party_size. A PER_PERSON price must be accompanied by a + // party size in the availability resources. If it is not, a party size of one + // is used. + PriceType_PER_PERSON PriceType = 1 +) + +var PriceType_name = map[int32]string{ + 0: "FIXED_RATE_DEFAULT", + 1: "PER_PERSON", +} +var PriceType_value = map[string]int32{ + "FIXED_RATE_DEFAULT": 0, + "PER_PERSON": 1, +} + +func (x PriceType) String() string { + return proto.EnumName(PriceType_name, int32(x)) +} +func (PriceType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{0} +} + +// Defines whether a credit card is required in order to book an appointment. +type RequireCreditCard int32 + +const ( + // The credit card requirement is not explicitly specified and the + // behaviour is identical to the one specified for CONDITIONAL. + RequireCreditCard_REQUIRE_CREDIT_CARD_UNSPECIFIED RequireCreditCard = 0 + // Google will require a credit card for the booking if any of the following + // conditions are met: + // * the availability has a price and the prepayment_type is REQUIRED + // * the no_show_fee is set + // * the deposit field is set. + RequireCreditCard_REQUIRE_CREDIT_CARD_CONDITIONAL RequireCreditCard = 1 + // A credit card is always required in order to book this availability + // regardless of other field values. + RequireCreditCard_REQUIRE_CREDIT_CARD_ALWAYS RequireCreditCard = 2 +) + +var RequireCreditCard_name = map[int32]string{ + 0: "REQUIRE_CREDIT_CARD_UNSPECIFIED", + 1: "REQUIRE_CREDIT_CARD_CONDITIONAL", + 2: "REQUIRE_CREDIT_CARD_ALWAYS", +} +var RequireCreditCard_value = map[string]int32{ + "REQUIRE_CREDIT_CARD_UNSPECIFIED": 0, + "REQUIRE_CREDIT_CARD_CONDITIONAL": 1, + "REQUIRE_CREDIT_CARD_ALWAYS": 2, +} + +func (x RequireCreditCard) String() string { + return proto.EnumName(RequireCreditCard_name, int32(x)) +} +func (RequireCreditCard) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{1} +} + +// The platform that the action is performed on. Web application is the general +// fallback. It is recommended to have at least one ActionLink with +// ACTION_PLATFORM_WEB_APPLICATION. Links with Android and iOS as platform are +// only used on the respective system. +type ActionPlatform int32 + +const ( + // The platform is unspecified. + ActionPlatform_ACTION_PLATFORM_UNSPECIFIED ActionPlatform = 0 + // The action platform is web in general. + ActionPlatform_ACTION_PLATFORM_WEB_APPLICATION ActionPlatform = 1 + // The action platform is web on mobile devices. + ActionPlatform_ACTION_PLATFORM_MOBILE_WEB ActionPlatform = 2 + // The action platform is Android OS. + ActionPlatform_ACTION_PLATFORM_ANDROID ActionPlatform = 3 + // The action platform is iOS. + ActionPlatform_ACTION_PLATFORM_IOS ActionPlatform = 4 +) + +var ActionPlatform_name = map[int32]string{ + 0: "ACTION_PLATFORM_UNSPECIFIED", + 1: "ACTION_PLATFORM_WEB_APPLICATION", + 2: "ACTION_PLATFORM_MOBILE_WEB", + 3: "ACTION_PLATFORM_ANDROID", + 4: "ACTION_PLATFORM_IOS", +} +var ActionPlatform_value = map[string]int32{ + "ACTION_PLATFORM_UNSPECIFIED": 0, + "ACTION_PLATFORM_WEB_APPLICATION": 1, + "ACTION_PLATFORM_MOBILE_WEB": 2, + "ACTION_PLATFORM_ANDROID": 3, + "ACTION_PLATFORM_IOS": 4, +} + +func (x ActionPlatform) String() string { + return proto.EnumName(ActionPlatform_name, int32(x)) +} +func (ActionPlatform) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{2} +} + +type FeedMetadata_ProcessingInstruction int32 + +const ( + // By default we will assume that this feed is an incremental feed. + FeedMetadata_PROCESS_UNKNOWN FeedMetadata_ProcessingInstruction = 0 + // This Feed message is one shard of a complete feed. Anything previously + // supplied by this partner will be deleted; the contents of this feed + // represent the entire state of the world. + FeedMetadata_PROCESS_AS_COMPLETE FeedMetadata_ProcessingInstruction = 1 + // This Feed message is one shard of an incremental feed. Existing entities + // will be left untouched except as modified in this feed. + FeedMetadata_PROCESS_AS_INCREMENTAL FeedMetadata_ProcessingInstruction = 2 +) + +var FeedMetadata_ProcessingInstruction_name = map[int32]string{ + 0: "PROCESS_UNKNOWN", + 1: "PROCESS_AS_COMPLETE", + 2: "PROCESS_AS_INCREMENTAL", +} +var FeedMetadata_ProcessingInstruction_value = map[string]int32{ + "PROCESS_UNKNOWN": 0, + "PROCESS_AS_COMPLETE": 1, + "PROCESS_AS_INCREMENTAL": 2, +} + +func (x FeedMetadata_ProcessingInstruction) String() string { + return proto.EnumName(FeedMetadata_ProcessingInstruction_name, int32(x)) +} +func (FeedMetadata_ProcessingInstruction) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{0, 0} +} + +// Enum to indicate the prepayment type. +type Service_PrepaymentType int32 + +const ( + // By default we will assume that the prepayment is NOT_SUPPORTED. + Service_PREPAYMENT_TYPE_UNSPECIFIED Service_PrepaymentType = 0 + // The user has to pay this service at the booking time. + Service_REQUIRED Service_PrepaymentType = 1 + // The user can choose to pre-pay this service at the booking time or later, + // but it is not required in order to book. + Service_OPTIONAL Service_PrepaymentType = 2 + // The prepayment is not supported for this service. + Service_NOT_SUPPORTED Service_PrepaymentType = 3 +) + +var Service_PrepaymentType_name = map[int32]string{ + 0: "PREPAYMENT_TYPE_UNSPECIFIED", + 1: "REQUIRED", + 2: "OPTIONAL", + 3: "NOT_SUPPORTED", +} +var Service_PrepaymentType_value = map[string]int32{ + "PREPAYMENT_TYPE_UNSPECIFIED": 0, + "REQUIRED": 1, + "OPTIONAL": 2, + "NOT_SUPPORTED": 3, +} + +func (x Service_PrepaymentType) String() string { + return proto.EnumName(Service_PrepaymentType_name, int32(x)) +} +func (Service_PrepaymentType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{7, 0} +} + +type Service_ServiceType int32 + +const ( + Service_SERVICE_TYPE_UNSPECIFIED Service_ServiceType = 0 + Service_SERVICE_TYPE_DINING_RESERVATION Service_ServiceType = 1 + Service_SERVICE_TYPE_FOOD_ORDERING Service_ServiceType = 2 + Service_SERVICE_TYPE_EVENT_TICKET Service_ServiceType = 3 + Service_SERVICE_TYPE_TRIP_TOUR Service_ServiceType = 4 +) + +var Service_ServiceType_name = map[int32]string{ + 0: "SERVICE_TYPE_UNSPECIFIED", + 1: "SERVICE_TYPE_DINING_RESERVATION", + 2: "SERVICE_TYPE_FOOD_ORDERING", + 3: "SERVICE_TYPE_EVENT_TICKET", + 4: "SERVICE_TYPE_TRIP_TOUR", +} +var Service_ServiceType_value = map[string]int32{ + "SERVICE_TYPE_UNSPECIFIED": 0, + "SERVICE_TYPE_DINING_RESERVATION": 1, + "SERVICE_TYPE_FOOD_ORDERING": 2, + "SERVICE_TYPE_EVENT_TICKET": 3, + "SERVICE_TYPE_TRIP_TOUR": 4, +} + +func (x Service_ServiceType) String() string { + return proto.EnumName(Service_ServiceType_name, int32(x)) +} +func (Service_ServiceType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{7, 1} +} + +// Enum to indicate the type of field. +type ServiceIntakeFormField_FieldType int32 + +const ( + // Fields of unspecified or unknown type will be ignored. + ServiceIntakeFormField_FIELD_TYPE_UNSPECIFIED ServiceIntakeFormField_FieldType = 0 + // A one-line input field for text. + ServiceIntakeFormField_SHORT_ANSWER ServiceIntakeFormField_FieldType = 1 + // A multi-line input field for text. + ServiceIntakeFormField_PARAGRAPH ServiceIntakeFormField_FieldType = 2 + // A set of radio buttons that requires one choice from many options. + ServiceIntakeFormField_MULTIPLE_CHOICE ServiceIntakeFormField_FieldType = 3 + // One or more enumerated items with checkboxes. + ServiceIntakeFormField_CHECKBOXES ServiceIntakeFormField_FieldType = 4 + // A selection from a dropdown. + ServiceIntakeFormField_DROPDOWN ServiceIntakeFormField_FieldType = 5 + // A yes/no button. + ServiceIntakeFormField_BOOLEAN ServiceIntakeFormField_FieldType = 6 +) + +var ServiceIntakeFormField_FieldType_name = map[int32]string{ + 0: "FIELD_TYPE_UNSPECIFIED", + 1: "SHORT_ANSWER", + 2: "PARAGRAPH", + 3: "MULTIPLE_CHOICE", + 4: "CHECKBOXES", + 5: "DROPDOWN", + 6: "BOOLEAN", +} +var ServiceIntakeFormField_FieldType_value = map[string]int32{ + "FIELD_TYPE_UNSPECIFIED": 0, + "SHORT_ANSWER": 1, + "PARAGRAPH": 2, + "MULTIPLE_CHOICE": 3, + "CHECKBOXES": 4, + "DROPDOWN": 5, + "BOOLEAN": 6, +} + +func (x ServiceIntakeFormField_FieldType) String() string { + return proto.EnumName(ServiceIntakeFormField_FieldType_name, int32(x)) +} +func (ServiceIntakeFormField_FieldType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{11, 0} +} + +// Enum to indicate the type of this media source. Only photos are supported. +// Please reach out to Google if supporting animations or videos is +// important to your feature and we will find the best solution for you. +type RelatedMedia_MediaType int32 + +const ( + // Unused. + RelatedMedia_TYPE_UNSPECIFIED RelatedMedia_MediaType = 0 + // Indicates the media provided by the url is a photo. + RelatedMedia_PHOTO RelatedMedia_MediaType = 1 +) + +var RelatedMedia_MediaType_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "PHOTO", +} +var RelatedMedia_MediaType_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "PHOTO": 1, +} + +func (x RelatedMedia_MediaType) String() string { + return proto.EnumName(RelatedMedia_MediaType_name, int32(x)) +} +func (RelatedMedia_MediaType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{17, 0} +} + +type FeedMetadata struct { + // Instructs us how to process the feed: either as a shard of a complete feed, + // or as a shard of an incremental update. + ProcessingInstruction FeedMetadata_ProcessingInstruction `protobuf:"varint,1,opt,name=processing_instruction,json=processingInstruction,enum=maps.booking.feeds.FeedMetadata_ProcessingInstruction" json:"processing_instruction,omitempty"` + // The current shard and total number of shards for this feed. + // + // Shard number is assumed to be zero-based. + // + // There does not need to be any relationship to the file name. + // + // Shards do not need to be transferred in order, and they may not be + // processed in order. + ShardNumber int32 `protobuf:"varint,2,opt,name=shard_number,json=shardNumber" json:"shard_number,omitempty"` + TotalShards int32 `protobuf:"varint,3,opt,name=total_shards,json=totalShards" json:"total_shards,omitempty"` + // An identifier that must be consistent across all shards in a feed. + // This value must be globally unique across each feed type. + // + // This value ensures that complete feeds spanning multiple shards are + // processed together correctly. + // + // Clients only need to set this value when the processing_instruction is set + // to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by + // total_shards). + // + // Feeds that span multiple shards must set this nonce to the same value. + Nonce uint64 `protobuf:"varint,5,opt,name=nonce" json:"nonce,omitempty"` + // The timestamp at which this feed shard was generated. + // + // In Unix time format (seconds since the epoch). + GenerationTimestamp int64 `protobuf:"varint,4,opt,name=generation_timestamp,json=generationTimestamp" json:"generation_timestamp,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FeedMetadata) Reset() { *m = FeedMetadata{} } +func (m *FeedMetadata) String() string { return proto.CompactTextString(m) } +func (*FeedMetadata) ProtoMessage() {} +func (*FeedMetadata) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{0} +} +func (m *FeedMetadata) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FeedMetadata.Unmarshal(m, b) +} +func (m *FeedMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FeedMetadata.Marshal(b, m, deterministic) +} +func (dst *FeedMetadata) XXX_Merge(src proto.Message) { + xxx_messageInfo_FeedMetadata.Merge(dst, src) +} +func (m *FeedMetadata) XXX_Size() int { + return xxx_messageInfo_FeedMetadata.Size(m) +} +func (m *FeedMetadata) XXX_DiscardUnknown() { + xxx_messageInfo_FeedMetadata.DiscardUnknown(m) +} + +var xxx_messageInfo_FeedMetadata proto.InternalMessageInfo + +func (m *FeedMetadata) GetProcessingInstruction() FeedMetadata_ProcessingInstruction { + if m != nil { + return m.ProcessingInstruction + } + return FeedMetadata_PROCESS_UNKNOWN +} + +func (m *FeedMetadata) GetShardNumber() int32 { + if m != nil { + return m.ShardNumber + } + return 0 +} + +func (m *FeedMetadata) GetTotalShards() int32 { + if m != nil { + return m.TotalShards + } + return 0 +} + +func (m *FeedMetadata) GetNonce() uint64 { + if m != nil { + return m.Nonce + } + return 0 +} + +func (m *FeedMetadata) GetGenerationTimestamp() int64 { + if m != nil { + return m.GenerationTimestamp + } + return 0 +} + +type AvailabilityFeed struct { + Metadata *FeedMetadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"` + ServiceAvailability []*ServiceAvailability `protobuf:"bytes,2,rep,name=service_availability,json=serviceAvailability" json:"service_availability,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AvailabilityFeed) Reset() { *m = AvailabilityFeed{} } +func (m *AvailabilityFeed) String() string { return proto.CompactTextString(m) } +func (*AvailabilityFeed) ProtoMessage() {} +func (*AvailabilityFeed) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{1} +} +func (m *AvailabilityFeed) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AvailabilityFeed.Unmarshal(m, b) +} +func (m *AvailabilityFeed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AvailabilityFeed.Marshal(b, m, deterministic) +} +func (dst *AvailabilityFeed) XXX_Merge(src proto.Message) { + xxx_messageInfo_AvailabilityFeed.Merge(dst, src) +} +func (m *AvailabilityFeed) XXX_Size() int { + return xxx_messageInfo_AvailabilityFeed.Size(m) +} +func (m *AvailabilityFeed) XXX_DiscardUnknown() { + xxx_messageInfo_AvailabilityFeed.DiscardUnknown(m) +} + +var xxx_messageInfo_AvailabilityFeed proto.InternalMessageInfo + +func (m *AvailabilityFeed) GetMetadata() *FeedMetadata { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *AvailabilityFeed) GetServiceAvailability() []*ServiceAvailability { + if m != nil { + return m.ServiceAvailability + } + return nil +} + +type ServiceAvailability struct { + // If provided, we will consider the Availability entities provided to be a + // complete snapshot from [start_timestamp_restrict, end_timestamp_restrict). + // That is, all existing availability will be deleted if the following + // condition holds true: + // + // start_timestamp_restrict <= Availability.start_sec && + // Availability.start_sec < end_timestamp_restrict + // + // If a resource_restrict message is set, the condition is further restricted: + // + // Availability.resource.staff_id == resource_restrict.staff_id && + // Availability.resource.room_id == resource_restrict.room_id + // + // These fields are typically used to provide a complete update of + // availability in a given time range. + // + // Setting start_timestamp_restrict while leaving end_timestamp_restrict unset + // is interpreted to mean all time beginning at start_timestamp_restrict. + // + // Setting end_timestamp_restrict while leaving start_timestamp_restrict unset + // is interpreted to mean all time up to the end_timestamp_restrict. + // + // In Unix time format (seconds since the epoch). + StartTimestampRestrict int64 `protobuf:"varint,1,opt,name=start_timestamp_restrict,json=startTimestampRestrict" json:"start_timestamp_restrict,omitempty"` + EndTimestampRestrict int64 `protobuf:"varint,2,opt,name=end_timestamp_restrict,json=endTimestampRestrict" json:"end_timestamp_restrict,omitempty"` + // If provided, the timestamp restricts will be applied only to the given + // merchant or service. + // + // These fields are typically used to provide complete snapshot of + // availability in a given range (defined above) for a specific merchant or + // service. + // + // Leaving these fields unset, or setting these to the empty string or null, + // is interpreted to mean that no restrict is intended. + MerchantIdRestrict string `protobuf:"bytes,3,opt,name=merchant_id_restrict,json=merchantIdRestrict" json:"merchant_id_restrict,omitempty"` + ServiceIdRestrict string `protobuf:"bytes,4,opt,name=service_id_restrict,json=serviceIdRestrict" json:"service_id_restrict,omitempty"` + // Setting resources_restrict further restricts the scope of the update to + // just this set of resources. All id fields of the resources must match + // exactly. + ResourcesRestrict *Resources `protobuf:"bytes,6,opt,name=resources_restrict,json=resourcesRestrict" json:"resources_restrict,omitempty"` + Availability []*Availability `protobuf:"bytes,5,rep,name=availability" json:"availability,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServiceAvailability) Reset() { *m = ServiceAvailability{} } +func (m *ServiceAvailability) String() string { return proto.CompactTextString(m) } +func (*ServiceAvailability) ProtoMessage() {} +func (*ServiceAvailability) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{2} +} +func (m *ServiceAvailability) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServiceAvailability.Unmarshal(m, b) +} +func (m *ServiceAvailability) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServiceAvailability.Marshal(b, m, deterministic) +} +func (dst *ServiceAvailability) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceAvailability.Merge(dst, src) +} +func (m *ServiceAvailability) XXX_Size() int { + return xxx_messageInfo_ServiceAvailability.Size(m) +} +func (m *ServiceAvailability) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceAvailability.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceAvailability proto.InternalMessageInfo + +func (m *ServiceAvailability) GetStartTimestampRestrict() int64 { + if m != nil { + return m.StartTimestampRestrict + } + return 0 +} + +func (m *ServiceAvailability) GetEndTimestampRestrict() int64 { + if m != nil { + return m.EndTimestampRestrict + } + return 0 +} + +func (m *ServiceAvailability) GetMerchantIdRestrict() string { + if m != nil { + return m.MerchantIdRestrict + } + return "" +} + +func (m *ServiceAvailability) GetServiceIdRestrict() string { + if m != nil { + return m.ServiceIdRestrict + } + return "" +} + +func (m *ServiceAvailability) GetResourcesRestrict() *Resources { + if m != nil { + return m.ResourcesRestrict + } + return nil +} + +func (m *ServiceAvailability) GetAvailability() []*Availability { + if m != nil { + return m.Availability + } + return nil +} + +// An availability of the merchant's service, indicating time and number +// of spots. +// The availability feed should be a list of this message. +// Please note that it's up to the partner to call out all the possible +// availabilities. +// If a massage therapist is available 9am-12pm, and they provide +// one-hour massage sessions, the aggregator should provide the feed as +// availability {start_sec: 9am, duration: 60 minutes, ...} +// availability {start_sec: 10am, duration: 60 minutes, ...} +// availability {start_sec: 11am, duration: 60 minutes, ...} +// instead of +// availability {start_sec: 9am, duration: 180 minutes, ...} +// +type Availability struct { + // An opaque string from an aggregator to identify a merchant. + MerchantId string `protobuf:"bytes,1,opt,name=merchant_id,json=merchantId" json:"merchant_id,omitempty"` + // An opaque string from aggregator to identify a service of the + // merchant. + ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId" json:"service_id,omitempty"` + // Start time of this availability, using epoch time in seconds. + StartSec int64 `protobuf:"varint,3,opt,name=start_sec,json=startSec" json:"start_sec,omitempty"` + // Duration of the service in seconds, e.g. 30 minutes for a chair massage. + DurationSec int64 `protobuf:"varint,4,opt,name=duration_sec,json=durationSec" json:"duration_sec,omitempty"` + // Number of total spots and open spots of this availability. + // E.g. a Yoga class of 10 spots with 3 booked. + // availability {spots_total: 10, spots_open: 7 ...} + // E.g. a chair massage session which was already booked. + // availability {spots_total: 1, spots_open: 0 ...} + // + // Note: If sending requests using the availability compression format defined + // below, these two fields will be inferred. A Recurrence + // implies spots_total=1 and spots_open=1. A ScheduleException implies + // spots_total=1 and spots_open=0. + SpotsTotal int64 `protobuf:"varint,5,opt,name=spots_total,json=spotsTotal" json:"spots_total,omitempty"` + SpotsOpen int64 `protobuf:"varint,6,opt,name=spots_open,json=spotsOpen" json:"spots_open,omitempty"` + // An optional opaque string to identify this availability slot. If set, it + // will be included in the requests that book/update/cancel appointments. + AvailabilityTag string `protobuf:"bytes,7,opt,name=availability_tag,json=availabilityTag" json:"availability_tag,omitempty"` + // Optional resources used to disambiguate this availability slot from + // others when different staff, room, or party_size values are part + // of the service. + // + // E.g. the same Yoga class with two 2 instructors. + // availability { resources { staff_id: "1" staff_name: "Amy" } + // spots_total: 10 spots_open: 7 } + // availability { resources { staff_id: "2" staff_name: "John" } + // spots_total: 5 spots_open: 2 } + Resources *Resources `protobuf:"bytes,8,opt,name=resources" json:"resources,omitempty"` + // A list of ids referencing the payment options which can be used to pay + // for this slot. The actual payment options are defined at the Merchant + // level, and can also be shared among multiple Merchants. + // + // This field overrides any payment_option_ids specified in the service + // message. Similarly payment_option_ids specified here do NOT have to be + // present in the service message, though must be defined at the + // Merchant level. + PaymentOptionId []string `protobuf:"bytes,9,rep,name=payment_option_id,json=paymentOptionId" json:"payment_option_id,omitempty"` + // The recurrence information for the availability, representing more than one + // start time. A recurrence should contain appointments for one working day. + Recurrence *Availability_Recurrence `protobuf:"bytes,10,opt,name=recurrence" json:"recurrence,omitempty"` + // When this service cannot be scheduled. To limit the number of + // schedule_exception messages consider joining adjacent exceptions. + ScheduleException []*Availability_ScheduleException `protobuf:"bytes,11,rep,name=schedule_exception,json=scheduleException" json:"schedule_exception,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Availability) Reset() { *m = Availability{} } +func (m *Availability) String() string { return proto.CompactTextString(m) } +func (*Availability) ProtoMessage() {} +func (*Availability) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{3} +} +func (m *Availability) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Availability.Unmarshal(m, b) +} +func (m *Availability) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Availability.Marshal(b, m, deterministic) +} +func (dst *Availability) XXX_Merge(src proto.Message) { + xxx_messageInfo_Availability.Merge(dst, src) +} +func (m *Availability) XXX_Size() int { + return xxx_messageInfo_Availability.Size(m) +} +func (m *Availability) XXX_DiscardUnknown() { + xxx_messageInfo_Availability.DiscardUnknown(m) +} + +var xxx_messageInfo_Availability proto.InternalMessageInfo + +func (m *Availability) GetMerchantId() string { + if m != nil { + return m.MerchantId + } + return "" +} + +func (m *Availability) GetServiceId() string { + if m != nil { + return m.ServiceId + } + return "" +} + +func (m *Availability) GetStartSec() int64 { + if m != nil { + return m.StartSec + } + return 0 +} + +func (m *Availability) GetDurationSec() int64 { + if m != nil { + return m.DurationSec + } + return 0 +} + +func (m *Availability) GetSpotsTotal() int64 { + if m != nil { + return m.SpotsTotal + } + return 0 +} + +func (m *Availability) GetSpotsOpen() int64 { + if m != nil { + return m.SpotsOpen + } + return 0 +} + +func (m *Availability) GetAvailabilityTag() string { + if m != nil { + return m.AvailabilityTag + } + return "" +} + +func (m *Availability) GetResources() *Resources { + if m != nil { + return m.Resources + } + return nil +} + +func (m *Availability) GetPaymentOptionId() []string { + if m != nil { + return m.PaymentOptionId + } + return nil +} + +func (m *Availability) GetRecurrence() *Availability_Recurrence { + if m != nil { + return m.Recurrence + } + return nil +} + +func (m *Availability) GetScheduleException() []*Availability_ScheduleException { + if m != nil { + return m.ScheduleException + } + return nil +} + +// Recurrence messages are optional, but allow for a more compact +// representation of consistently repeating availability slots. They typically +// represent a day's working schedule. +// ScheduleException messages are then used to represent booked/unavailable +// time ranges within the work day. +// +// Requirements: +// 1. The expansion of availability slots or recurrences must NOT create +// identical slots. If the ids, start_sec, duration_sec, and resources +// match, slots are considered identical. +// 2. Do NOT mix the standard availability format and recurrence within the +// slots of a single service. Recurrence benefits merchants/services that +// offer appointments. The standard format is geared towards +// merchants/services with regularly scheduled classes. +type Availability_Recurrence struct { + // The inclusive maximum UTC timestamp the availability repeats until. + RepeatUntilSec int64 `protobuf:"varint,1,opt,name=repeat_until_sec,json=repeatUntilSec" json:"repeat_until_sec,omitempty"` + // Defines the time between successive availability slots. + // + // E.g. An availability with a duration of 20 min, a repeat_every_sec of + // 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will + // yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am, + // 11-11:20am. + RepeatEverySec int32 `protobuf:"varint,2,opt,name=repeat_every_sec,json=repeatEverySec" json:"repeat_every_sec,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Availability_Recurrence) Reset() { *m = Availability_Recurrence{} } +func (m *Availability_Recurrence) String() string { return proto.CompactTextString(m) } +func (*Availability_Recurrence) ProtoMessage() {} +func (*Availability_Recurrence) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{3, 0} +} +func (m *Availability_Recurrence) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Availability_Recurrence.Unmarshal(m, b) +} +func (m *Availability_Recurrence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Availability_Recurrence.Marshal(b, m, deterministic) +} +func (dst *Availability_Recurrence) XXX_Merge(src proto.Message) { + xxx_messageInfo_Availability_Recurrence.Merge(dst, src) +} +func (m *Availability_Recurrence) XXX_Size() int { + return xxx_messageInfo_Availability_Recurrence.Size(m) +} +func (m *Availability_Recurrence) XXX_DiscardUnknown() { + xxx_messageInfo_Availability_Recurrence.DiscardUnknown(m) +} + +var xxx_messageInfo_Availability_Recurrence proto.InternalMessageInfo + +func (m *Availability_Recurrence) GetRepeatUntilSec() int64 { + if m != nil { + return m.RepeatUntilSec + } + return 0 +} + +func (m *Availability_Recurrence) GetRepeatEverySec() int32 { + if m != nil { + return m.RepeatEverySec + } + return 0 +} + +// ScheduleException messages are used to represent booked/unavailable time +// ranges within the work day. As time slots are booked, the list of +// exceptions should grow to reflect the newly unavailable time ranges. +// The recurrence itself shouldn't be modified. +type Availability_ScheduleException struct { + // The time range of the exception. + TimeRange *TimeRange `protobuf:"bytes,1,opt,name=time_range,json=timeRange" json:"time_range,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Availability_ScheduleException) Reset() { *m = Availability_ScheduleException{} } +func (m *Availability_ScheduleException) String() string { return proto.CompactTextString(m) } +func (*Availability_ScheduleException) ProtoMessage() {} +func (*Availability_ScheduleException) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{3, 1} +} +func (m *Availability_ScheduleException) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Availability_ScheduleException.Unmarshal(m, b) +} +func (m *Availability_ScheduleException) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Availability_ScheduleException.Marshal(b, m, deterministic) +} +func (dst *Availability_ScheduleException) XXX_Merge(src proto.Message) { + xxx_messageInfo_Availability_ScheduleException.Merge(dst, src) +} +func (m *Availability_ScheduleException) XXX_Size() int { + return xxx_messageInfo_Availability_ScheduleException.Size(m) +} +func (m *Availability_ScheduleException) XXX_DiscardUnknown() { + xxx_messageInfo_Availability_ScheduleException.DiscardUnknown(m) +} + +var xxx_messageInfo_Availability_ScheduleException proto.InternalMessageInfo + +func (m *Availability_ScheduleException) GetTimeRange() *TimeRange { + if m != nil { + return m.TimeRange + } + return nil +} + +// A resource is used to disambiguate availability slots from one another when +// different staff, room or party_size values are part of the service. +// Multiple slots for the same service and time interval can co-exist when they +// have different resources. +type Resources struct { + // Optional id for a staff member providing the service. This field identifies + // the staff member across all merchants, services, and availability records. + // It also needs to be stable over time to allow correlation with past + // bookings. + // This field must be present if staff_name is present. + StaffId string `protobuf:"bytes,1,opt,name=staff_id,json=staffId" json:"staff_id,omitempty"` + // Optional name of a staff member providing the service. This field will be + // displayed to users making a booking, and should be human readable, as + // opposed to an opaque identifier. + // This field must be present if staff_id is present. + StaffName string `protobuf:"bytes,2,opt,name=staff_name,json=staffName" json:"staff_name,omitempty"` + // An optional id for the room the service is located in. This field + // identifies the room across all merchants, services, and availability + // records. It also needs to be stable over time to allow correlation with + // past bookings. + // This field must be present if room_name is present. + RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId" json:"room_id,omitempty"` + // An optional name for the room the service is located in. This + // field will be displayed to users making a booking, and should be human + // readable, as opposed to an opaque identifier. + // This field must be present if room_id is present. + RoomName string `protobuf:"bytes,4,opt,name=room_name,json=roomName" json:"room_name,omitempty"` + // Applicable only for Dining: The party size which can be accommodated + // during this time slot. A restaurant can be associated with multiple Slots + // for the same time, each specifying a different party_size, if for instance + // 2, 3, or 4 people can be seated with a reservation. + PartySize int32 `protobuf:"varint,5,opt,name=party_size,json=partySize" json:"party_size,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Resources) Reset() { *m = Resources{} } +func (m *Resources) String() string { return proto.CompactTextString(m) } +func (*Resources) ProtoMessage() {} +func (*Resources) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{4} +} +func (m *Resources) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Resources.Unmarshal(m, b) +} +func (m *Resources) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Resources.Marshal(b, m, deterministic) +} +func (dst *Resources) XXX_Merge(src proto.Message) { + xxx_messageInfo_Resources.Merge(dst, src) +} +func (m *Resources) XXX_Size() int { + return xxx_messageInfo_Resources.Size(m) +} +func (m *Resources) XXX_DiscardUnknown() { + xxx_messageInfo_Resources.DiscardUnknown(m) +} + +var xxx_messageInfo_Resources proto.InternalMessageInfo + +func (m *Resources) GetStaffId() string { + if m != nil { + return m.StaffId + } + return "" +} + +func (m *Resources) GetStaffName() string { + if m != nil { + return m.StaffName + } + return "" +} + +func (m *Resources) GetRoomId() string { + if m != nil { + return m.RoomId + } + return "" +} + +func (m *Resources) GetRoomName() string { + if m != nil { + return m.RoomName + } + return "" +} + +func (m *Resources) GetPartySize() int32 { + if m != nil { + return m.PartySize + } + return 0 +} + +type TimeRange struct { + BeginSec int64 `protobuf:"varint,1,opt,name=begin_sec,json=beginSec" json:"begin_sec,omitempty"` + EndSec int64 `protobuf:"varint,2,opt,name=end_sec,json=endSec" json:"end_sec,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TimeRange) Reset() { *m = TimeRange{} } +func (m *TimeRange) String() string { return proto.CompactTextString(m) } +func (*TimeRange) ProtoMessage() {} +func (*TimeRange) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{5} +} +func (m *TimeRange) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TimeRange.Unmarshal(m, b) +} +func (m *TimeRange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TimeRange.Marshal(b, m, deterministic) +} +func (dst *TimeRange) XXX_Merge(src proto.Message) { + xxx_messageInfo_TimeRange.Merge(dst, src) +} +func (m *TimeRange) XXX_Size() int { + return xxx_messageInfo_TimeRange.Size(m) +} +func (m *TimeRange) XXX_DiscardUnknown() { + xxx_messageInfo_TimeRange.DiscardUnknown(m) +} + +var xxx_messageInfo_TimeRange proto.InternalMessageInfo + +func (m *TimeRange) GetBeginSec() int64 { + if m != nil { + return m.BeginSec + } + return 0 +} + +func (m *TimeRange) GetEndSec() int64 { + if m != nil { + return m.EndSec + } + return 0 +} + +type ServiceFeed struct { + Metadata *FeedMetadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"` + Service []*Service `protobuf:"bytes,2,rep,name=service" json:"service,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServiceFeed) Reset() { *m = ServiceFeed{} } +func (m *ServiceFeed) String() string { return proto.CompactTextString(m) } +func (*ServiceFeed) ProtoMessage() {} +func (*ServiceFeed) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{6} +} +func (m *ServiceFeed) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServiceFeed.Unmarshal(m, b) +} +func (m *ServiceFeed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServiceFeed.Marshal(b, m, deterministic) +} +func (dst *ServiceFeed) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceFeed.Merge(dst, src) +} +func (m *ServiceFeed) XXX_Size() int { + return xxx_messageInfo_ServiceFeed.Size(m) +} +func (m *ServiceFeed) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceFeed.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceFeed proto.InternalMessageInfo + +func (m *ServiceFeed) GetMetadata() *FeedMetadata { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *ServiceFeed) GetService() []*Service { + if m != nil { + return m.Service + } + return nil +} + +// The definition of a service provided by a merchant. +type Service struct { + // An opaque string from an aggregator partner which uniquely identifies a + // merchant. (required) + MerchantId string `protobuf:"bytes,1,opt,name=merchant_id,json=merchantId" json:"merchant_id,omitempty"` + // An opaque string from an aggregator partner which uniquely identifies the + // service. (required) + ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId" json:"service_id,omitempty"` + // The name of the service, suitable for display to users, e.g. "Men's + // haircut". (required) + Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` + // The description of the service, suitable for display to users. + // + // This field now supports both plain text and HTML-like formatting rules to + // display structural contents to end-users. Unlike plain text sections, + // customized layouts can be created here using headings, paragraphs, lists + // and some phrase tags. Please read the following instructions and notes + // carefully to create better user-experience. + // + // Supported HTML-like formatting tags: + // + // Heading tags: <h1>, <h2>, <h3>, <h4>, <h5>, <h6> + // Heading tags can be used to display titles and sub-titles. For example, + // <h1>Itinerary</h1> will display the inline text as the most important + // heading of the section. Note that any inner HTML tags, styles or + // attributes will be ignored. For example, <h1 style=".."> will be treated + // the same as <h1>. Only pure text wil be preserved. + // + // Paragraph tag: <p>: + // Paragraph tag can be used to wrap detailed introduction or contents. Any + // inner tags, styles or attributes will be ignored with a few exceptions: + // <br>, <strong> and <em>. Please see the phrase tag section below for more + // details. + // + // List tags: <ul>, <ol>, <li> + // <ul> tag can be used with <li> tag to display unordered lists, and <ol> + // tag can be used with <li> to display ordered lists. This is a good tool + // to display checklists, highlights or any other lists that fit your + // use-cases. + // Example: To show a list of features of a cruise trip: + // <ol> + // <li>Wonderful ocean view and chances to play with wildlife.</li> + // <li>Carefully designed travel arrangements and services.</li> + // <li>Gauranteed lowest price.</li> + // </ol> + // Note that only <li> children under <ul> or <ol> tags will be converted. All + // other children will be dropped. Also any inner tags, attributes and styles + // will be ignored, only pure text contents are preserved. + // + // Division tag: <div> + // All supported inner tags of the <div> tag will be parsed with the rules + // stated above. But <div> tag itself does not mean any grouping or + // indenting here. Also any inner attributes and styles will be ignored. + // + // Phrase tags: <br>, <strong>, <em>: + // Only the three tags mentioned above are supported. <br> can be used to + // break lines in paragraphs, and <strong>/<em> can be used to highlight + // important text. Any other phrase tags will be ignored. + // + // Unsupported tags: + // * <html>, <header> and <body> tags are not allowed. + // * Any URLs, anchors, links will be striped, and will never be displayed + // to end-users. If you want to use photos to create a rich user experience, + // please use the "related_media" field below to send your photo urls + // * Any other tags not mentioned above are not supported (for example + // <table>, <td> ...). + // + // Important notes: + // * Try not to use other tags except for the supported ones mentioned + // above, because contents within unsupported tags will be excluded from UI + // rendering and may lead to undesired user experience. + // * Try avoid deep nested structures like more than 3 different heading + // levels or nested lists. Keeping the structure flat, simple and + // straightforward helps to create a better user experience. + // * If the currently supported layouts are not enough for your use cases, + // please contact Google with your requests and we will find the best + // approach for you. + // + // (required) + Description string `protobuf:"bytes,4,opt,name=description" json:"description,omitempty"` + // The price of the service. (optional, overridden when payment options or + // ticket types present) + Price *Price `protobuf:"bytes,5,opt,name=price" json:"price,omitempty"` + // Rules to book/cancel an appointment. (optional) + Rules *SchedulingRules `protobuf:"bytes,6,opt,name=rules" json:"rules,omitempty"` + // Intake forms to customize the service. (optional) + Form []*ServiceIntakeForm `protobuf:"bytes,7,rep,name=form" json:"form,omitempty"` + // Whether a prepayment is required, optional or not supported. (optional) + PrepaymentType Service_PrepaymentType `protobuf:"varint,8,opt,name=prepayment_type,json=prepaymentType,enum=maps.booking.feeds.Service_PrepaymentType" json:"prepayment_type,omitempty"` + // The service's tax rate. If present this field overrides any tax_rate set at + // the merchant level. An empty message (i.e. tax_rate { }) will reset the + // applied tax rate to zero. (optional) + TaxRate *TaxRate `protobuf:"bytes,9,opt,name=tax_rate,json=taxRate" json:"tax_rate,omitempty"` + // A list of ids referencing the payment options which can be used to pay + // for this service. The actual payment options are defined at the Merchant + // level, and can also be shared among multiple Merchants. (optional) + PaymentOptionId []string `protobuf:"bytes,10,rep,name=payment_option_id,json=paymentOptionId" json:"payment_option_id,omitempty"` + // Defines how a deposit may be charged to the user. Can be overridden at the + // availability level. (optional) + Deposit *Deposit `protobuf:"bytes,11,opt,name=deposit" json:"deposit,omitempty"` + // Defines a no show fee that may be charged to the user. Can be overridden + // at the availability level. (optional) + NoShowFee *NoShowFee `protobuf:"bytes,12,opt,name=no_show_fee,json=noShowFee" json:"no_show_fee,omitempty"` + // Indicates whether the user must provide a credit card in order to book this + // service. + // This value can be overridden at the availability level. (optional) + RequireCreditCard RequireCreditCard `protobuf:"varint,13,opt,name=require_credit_card,json=requireCreditCard,enum=maps.booking.feeds.RequireCreditCard" json:"require_credit_card,omitempty"` + // An action link related to this service. + ActionLink []*ActionLink `protobuf:"bytes,14,rep,name=action_link,json=actionLink" json:"action_link,omitempty"` + // The predefined type of this service. Currently, only used with action_link. + Type Service_ServiceType `protobuf:"varint,15,opt,name=type,enum=maps.booking.feeds.Service_ServiceType" json:"type,omitempty"` + // Types of tickets that can be booked/purchased for this service, if tickets + // are supported. (optional) + TicketType []*TicketType `protobuf:"bytes,16,rep,name=ticket_type,json=ticketType" json:"ticket_type,omitempty"` + // Photos related to this service. Google will crawl these media to + // ensure that they are displayed to end-users in the most efficient way. + // (optional) + RelatedMedia []*RelatedMedia `protobuf:"bytes,17,rep,name=related_media,json=relatedMedia" json:"related_media,omitempty"` + // Service attribute values that apply to this service (optional). + // Each Service may have zero or more values for each service attribute + // defined in the corresponding Merchant. + ServiceAttributeValueId []*ServiceAttributeValueId `protobuf:"bytes,18,rep,name=service_attribute_value_id,json=serviceAttributeValueId" json:"service_attribute_value_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Service) Reset() { *m = Service{} } +func (m *Service) String() string { return proto.CompactTextString(m) } +func (*Service) ProtoMessage() {} +func (*Service) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{7} +} +func (m *Service) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Service.Unmarshal(m, b) +} +func (m *Service) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Service.Marshal(b, m, deterministic) +} +func (dst *Service) XXX_Merge(src proto.Message) { + xxx_messageInfo_Service.Merge(dst, src) +} +func (m *Service) XXX_Size() int { + return xxx_messageInfo_Service.Size(m) +} +func (m *Service) XXX_DiscardUnknown() { + xxx_messageInfo_Service.DiscardUnknown(m) +} + +var xxx_messageInfo_Service proto.InternalMessageInfo + +func (m *Service) GetMerchantId() string { + if m != nil { + return m.MerchantId + } + return "" +} + +func (m *Service) GetServiceId() string { + if m != nil { + return m.ServiceId + } + return "" +} + +func (m *Service) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Service) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *Service) GetPrice() *Price { + if m != nil { + return m.Price + } + return nil +} + +func (m *Service) GetRules() *SchedulingRules { + if m != nil { + return m.Rules + } + return nil +} + +func (m *Service) GetForm() []*ServiceIntakeForm { + if m != nil { + return m.Form + } + return nil +} + +func (m *Service) GetPrepaymentType() Service_PrepaymentType { + if m != nil { + return m.PrepaymentType + } + return Service_PREPAYMENT_TYPE_UNSPECIFIED +} + +func (m *Service) GetTaxRate() *TaxRate { + if m != nil { + return m.TaxRate + } + return nil +} + +func (m *Service) GetPaymentOptionId() []string { + if m != nil { + return m.PaymentOptionId + } + return nil +} + +func (m *Service) GetDeposit() *Deposit { + if m != nil { + return m.Deposit + } + return nil +} + +func (m *Service) GetNoShowFee() *NoShowFee { + if m != nil { + return m.NoShowFee + } + return nil +} + +func (m *Service) GetRequireCreditCard() RequireCreditCard { + if m != nil { + return m.RequireCreditCard + } + return RequireCreditCard_REQUIRE_CREDIT_CARD_UNSPECIFIED +} + +func (m *Service) GetActionLink() []*ActionLink { + if m != nil { + return m.ActionLink + } + return nil +} + +func (m *Service) GetType() Service_ServiceType { + if m != nil { + return m.Type + } + return Service_SERVICE_TYPE_UNSPECIFIED +} + +func (m *Service) GetTicketType() []*TicketType { + if m != nil { + return m.TicketType + } + return nil +} + +func (m *Service) GetRelatedMedia() []*RelatedMedia { + if m != nil { + return m.RelatedMedia + } + return nil +} + +func (m *Service) GetServiceAttributeValueId() []*ServiceAttributeValueId { + if m != nil { + return m.ServiceAttributeValueId + } + return nil +} + +// The price of a service or a fee. +type Price struct { + // The price in micro-units of the currency. + // Fractions of smallest currency unit will be rounded using nearest even + // rounding. (e.g. For USD 2.5 cents rounded to 2 cents, 3.5 cents rounded to + // 4 cents, 0.5 cents rounded to 0 cents, 2.51 cents rounded to 3 cents). + // (required) + PriceMicros int64 `protobuf:"varint,1,opt,name=price_micros,json=priceMicros" json:"price_micros,omitempty"` + // The currency of the price that is defined in ISO 4217. (required) + CurrencyCode string `protobuf:"bytes,2,opt,name=currency_code,json=currencyCode" json:"currency_code,omitempty"` + // An optional and opaque string that identifies the pricing option that is + // associated with the extended price. (optional) + PricingOptionTag string `protobuf:"bytes,3,opt,name=pricing_option_tag,json=pricingOptionTag" json:"pricing_option_tag,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Price) Reset() { *m = Price{} } +func (m *Price) String() string { return proto.CompactTextString(m) } +func (*Price) ProtoMessage() {} +func (*Price) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{8} +} +func (m *Price) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Price.Unmarshal(m, b) +} +func (m *Price) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Price.Marshal(b, m, deterministic) +} +func (dst *Price) XXX_Merge(src proto.Message) { + xxx_messageInfo_Price.Merge(dst, src) +} +func (m *Price) XXX_Size() int { + return xxx_messageInfo_Price.Size(m) +} +func (m *Price) XXX_DiscardUnknown() { + xxx_messageInfo_Price.DiscardUnknown(m) +} + +var xxx_messageInfo_Price proto.InternalMessageInfo + +func (m *Price) GetPriceMicros() int64 { + if m != nil { + return m.PriceMicros + } + return 0 +} + +func (m *Price) GetCurrencyCode() string { + if m != nil { + return m.CurrencyCode + } + return "" +} + +func (m *Price) GetPricingOptionTag() string { + if m != nil { + return m.PricingOptionTag + } + return "" +} + +// The scheduling rules for a service. +type SchedulingRules struct { + // The minimum advance notice in seconds required to book an appointment. + // (optional) + MinAdvanceBooking int64 `protobuf:"varint,1,opt,name=min_advance_booking,json=minAdvanceBooking" json:"min_advance_booking,omitempty"` + // The minimum advance notice in seconds required to cancel a booked + // appointment online. (optional) + MinAdvanceOnlineCanceling int64 `protobuf:"varint,2,opt,name=min_advance_online_canceling,json=minAdvanceOnlineCanceling" json:"min_advance_online_canceling,omitempty"` + // The fee for canceling within the minimum advance notice period. + LateCancellationFee *Price `protobuf:"bytes,3,opt,name=late_cancellation_fee,json=lateCancellationFee" json:"late_cancellation_fee,omitempty"` // Deprecated: Do not use. + // The fee for no-show without canceling. + NoshowFee *Price `protobuf:"bytes,4,opt,name=noshow_fee,json=noshowFee" json:"noshow_fee,omitempty"` // Deprecated: Do not use. + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SchedulingRules) Reset() { *m = SchedulingRules{} } +func (m *SchedulingRules) String() string { return proto.CompactTextString(m) } +func (*SchedulingRules) ProtoMessage() {} +func (*SchedulingRules) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{9} +} +func (m *SchedulingRules) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SchedulingRules.Unmarshal(m, b) +} +func (m *SchedulingRules) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SchedulingRules.Marshal(b, m, deterministic) +} +func (dst *SchedulingRules) XXX_Merge(src proto.Message) { + xxx_messageInfo_SchedulingRules.Merge(dst, src) +} +func (m *SchedulingRules) XXX_Size() int { + return xxx_messageInfo_SchedulingRules.Size(m) +} +func (m *SchedulingRules) XXX_DiscardUnknown() { + xxx_messageInfo_SchedulingRules.DiscardUnknown(m) +} + +var xxx_messageInfo_SchedulingRules proto.InternalMessageInfo + +func (m *SchedulingRules) GetMinAdvanceBooking() int64 { + if m != nil { + return m.MinAdvanceBooking + } + return 0 +} + +func (m *SchedulingRules) GetMinAdvanceOnlineCanceling() int64 { + if m != nil { + return m.MinAdvanceOnlineCanceling + } + return 0 +} + +// Deprecated: Do not use. +func (m *SchedulingRules) GetLateCancellationFee() *Price { + if m != nil { + return m.LateCancellationFee + } + return nil +} + +// Deprecated: Do not use. +func (m *SchedulingRules) GetNoshowFee() *Price { + if m != nil { + return m.NoshowFee + } + return nil +} + +// A tax rate applied when charging the user for a service, and which can be set +// on either a per merchant, or per service basis. +type TaxRate struct { + // A tax rate in millionths of one percent, effectively giving 6 decimals of + // precision. For example, if the tax rate is 7.253%, this field should be set + // to 7253000. + // + // If this field is left unset or set to 0, the total price charged to a user + // for any service provided by this merchant is the exact price specified by + // Service.price. The service price is assumed to be exempt from or already + // inclusive of applicable taxes. Taxes will not be shown to the user as a + // separate line item. + // + // If this field is set to any nonzero value, the total price charged to a + // user for any service provided by this merchant will include the service + // price plus the tax assessed using the tax rate provided here. Fractions of + // the smallest currency unit (for example, fractions of one cent) will be + // rounded using nearest even rounding. Taxes will be shown to the user as a + // separate line item. (required) + MicroPercent int32 `protobuf:"varint,1,opt,name=micro_percent,json=microPercent" json:"micro_percent,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TaxRate) Reset() { *m = TaxRate{} } +func (m *TaxRate) String() string { return proto.CompactTextString(m) } +func (*TaxRate) ProtoMessage() {} +func (*TaxRate) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{10} +} +func (m *TaxRate) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TaxRate.Unmarshal(m, b) +} +func (m *TaxRate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TaxRate.Marshal(b, m, deterministic) +} +func (dst *TaxRate) XXX_Merge(src proto.Message) { + xxx_messageInfo_TaxRate.Merge(dst, src) +} +func (m *TaxRate) XXX_Size() int { + return xxx_messageInfo_TaxRate.Size(m) +} +func (m *TaxRate) XXX_DiscardUnknown() { + xxx_messageInfo_TaxRate.DiscardUnknown(m) +} + +var xxx_messageInfo_TaxRate proto.InternalMessageInfo + +func (m *TaxRate) GetMicroPercent() int32 { + if m != nil { + return m.MicroPercent + } + return 0 +} + +// Defines a field that is included in a ServiceIntakeForm. +type ServiceIntakeFormField struct { + // The type of this field. (required) + Type ServiceIntakeFormField_FieldType `protobuf:"varint,1,opt,name=type,enum=maps.booking.feeds.ServiceIntakeFormField_FieldType" json:"type,omitempty"` + // The text shown to the user for this field. (required) + Label string `protobuf:"bytes,2,opt,name=label" json:"label,omitempty"` + // For MULTIPLE_CHOICE, CHECKBOXES, or DROPDOWN, the values to enumerate. + // (optional) + Value []string `protobuf:"bytes,3,rep,name=value" json:"value,omitempty"` + // Indicates whether an answer to this field is required by a user. (optional) + IsRequired bool `protobuf:"varint,4,opt,name=is_required,json=isRequired" json:"is_required,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServiceIntakeFormField) Reset() { *m = ServiceIntakeFormField{} } +func (m *ServiceIntakeFormField) String() string { return proto.CompactTextString(m) } +func (*ServiceIntakeFormField) ProtoMessage() {} +func (*ServiceIntakeFormField) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{11} +} +func (m *ServiceIntakeFormField) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServiceIntakeFormField.Unmarshal(m, b) +} +func (m *ServiceIntakeFormField) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServiceIntakeFormField.Marshal(b, m, deterministic) +} +func (dst *ServiceIntakeFormField) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceIntakeFormField.Merge(dst, src) +} +func (m *ServiceIntakeFormField) XXX_Size() int { + return xxx_messageInfo_ServiceIntakeFormField.Size(m) +} +func (m *ServiceIntakeFormField) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceIntakeFormField.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceIntakeFormField proto.InternalMessageInfo + +func (m *ServiceIntakeFormField) GetType() ServiceIntakeFormField_FieldType { + if m != nil { + return m.Type + } + return ServiceIntakeFormField_FIELD_TYPE_UNSPECIFIED +} + +func (m *ServiceIntakeFormField) GetLabel() string { + if m != nil { + return m.Label + } + return "" +} + +func (m *ServiceIntakeFormField) GetValue() []string { + if m != nil { + return m.Value + } + return nil +} + +func (m *ServiceIntakeFormField) GetIsRequired() bool { + if m != nil { + return m.IsRequired + } + return false +} + +// Defines an intake form that customizes the service provided by a merchant. +type ServiceIntakeForm struct { + // Fields that will be displayed to the user. (required) + Field []*ServiceIntakeFormField `protobuf:"bytes,1,rep,name=field" json:"field,omitempty"` + // If true, this form will be shown to first time customers. + // (one of first_time_customers or returning_customers required) + FirstTimeCustomers bool `protobuf:"varint,2,opt,name=first_time_customers,json=firstTimeCustomers" json:"first_time_customers,omitempty"` + // If true, this form will be shown to repeat customers. + // (one of first_time_customers or returning_customers required) + ReturningCustomers bool `protobuf:"varint,3,opt,name=returning_customers,json=returningCustomers" json:"returning_customers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServiceIntakeForm) Reset() { *m = ServiceIntakeForm{} } +func (m *ServiceIntakeForm) String() string { return proto.CompactTextString(m) } +func (*ServiceIntakeForm) ProtoMessage() {} +func (*ServiceIntakeForm) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{12} +} +func (m *ServiceIntakeForm) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServiceIntakeForm.Unmarshal(m, b) +} +func (m *ServiceIntakeForm) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServiceIntakeForm.Marshal(b, m, deterministic) +} +func (dst *ServiceIntakeForm) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceIntakeForm.Merge(dst, src) +} +func (m *ServiceIntakeForm) XXX_Size() int { + return xxx_messageInfo_ServiceIntakeForm.Size(m) +} +func (m *ServiceIntakeForm) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceIntakeForm.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceIntakeForm proto.InternalMessageInfo + +func (m *ServiceIntakeForm) GetField() []*ServiceIntakeFormField { + if m != nil { + return m.Field + } + return nil +} + +func (m *ServiceIntakeForm) GetFirstTimeCustomers() bool { + if m != nil { + return m.FirstTimeCustomers + } + return false +} + +func (m *ServiceIntakeForm) GetReturningCustomers() bool { + if m != nil { + return m.ReturningCustomers + } + return false +} + +// A deposit that the user may be charged or have a hold on their credit card +// for. +type Deposit struct { + // Deposit amount. + Deposit *Price `protobuf:"bytes,1,opt,name=deposit" json:"deposit,omitempty"` + // Minimum advance cancellation for the deposit. + MinAdvanceCancellationSec int64 `protobuf:"varint,2,opt,name=min_advance_cancellation_sec,json=minAdvanceCancellationSec" json:"min_advance_cancellation_sec,omitempty"` + // Defines how the deposit is determined from the availability. + DepositType PriceType `protobuf:"varint,3,opt,name=deposit_type,json=depositType,enum=maps.booking.feeds.PriceType" json:"deposit_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Deposit) Reset() { *m = Deposit{} } +func (m *Deposit) String() string { return proto.CompactTextString(m) } +func (*Deposit) ProtoMessage() {} +func (*Deposit) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{13} +} +func (m *Deposit) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Deposit.Unmarshal(m, b) +} +func (m *Deposit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Deposit.Marshal(b, m, deterministic) +} +func (dst *Deposit) XXX_Merge(src proto.Message) { + xxx_messageInfo_Deposit.Merge(dst, src) +} +func (m *Deposit) XXX_Size() int { + return xxx_messageInfo_Deposit.Size(m) +} +func (m *Deposit) XXX_DiscardUnknown() { + xxx_messageInfo_Deposit.DiscardUnknown(m) +} + +var xxx_messageInfo_Deposit proto.InternalMessageInfo + +func (m *Deposit) GetDeposit() *Price { + if m != nil { + return m.Deposit + } + return nil +} + +func (m *Deposit) GetMinAdvanceCancellationSec() int64 { + if m != nil { + return m.MinAdvanceCancellationSec + } + return 0 +} + +func (m *Deposit) GetDepositType() PriceType { + if m != nil { + return m.DepositType + } + return PriceType_FIXED_RATE_DEFAULT +} + +// A fee that a user may be charged if they have made a booking but do not +// show up. +type NoShowFee struct { + // The amount the user may be charged if they do not show up for their + // reservation. + Fee *Price `protobuf:"bytes,1,opt,name=fee" json:"fee,omitempty"` + // Defines how the fee is determined from the availability. + FeeType PriceType `protobuf:"varint,3,opt,name=fee_type,json=feeType,enum=maps.booking.feeds.PriceType" json:"fee_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *NoShowFee) Reset() { *m = NoShowFee{} } +func (m *NoShowFee) String() string { return proto.CompactTextString(m) } +func (*NoShowFee) ProtoMessage() {} +func (*NoShowFee) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{14} +} +func (m *NoShowFee) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_NoShowFee.Unmarshal(m, b) +} +func (m *NoShowFee) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_NoShowFee.Marshal(b, m, deterministic) +} +func (dst *NoShowFee) XXX_Merge(src proto.Message) { + xxx_messageInfo_NoShowFee.Merge(dst, src) +} +func (m *NoShowFee) XXX_Size() int { + return xxx_messageInfo_NoShowFee.Size(m) +} +func (m *NoShowFee) XXX_DiscardUnknown() { + xxx_messageInfo_NoShowFee.DiscardUnknown(m) +} + +var xxx_messageInfo_NoShowFee proto.InternalMessageInfo + +func (m *NoShowFee) GetFee() *Price { + if m != nil { + return m.Fee + } + return nil +} + +func (m *NoShowFee) GetFeeType() PriceType { + if m != nil { + return m.FeeType + } + return PriceType_FIXED_RATE_DEFAULT +} + +// An action URL with associated language, list of countries restricted to, and +// optional platform that indicates which platform this action should be +// performed on. +type ActionLink struct { + // The entry point URL for this action link. + Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"` + // The BCP-47 language tag identifying the language in which the content + // from this URI is available. + Language string `protobuf:"bytes,2,opt,name=language" json:"language,omitempty"` + // ISO 3166-1 alpha-2 country code. Leave empty for unrestricted visibility. + RestrictedCountry []string `protobuf:"bytes,3,rep,name=restricted_country,json=restrictedCountry" json:"restricted_country,omitempty"` + // The platform that this action should be performed on. If this field is + // unset, ACTION_PLATFORM_WEB_APPLICATION will be used as fallback. + Platform ActionPlatform `protobuf:"varint,4,opt,name=platform,enum=maps.booking.feeds.ActionPlatform" json:"platform,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ActionLink) Reset() { *m = ActionLink{} } +func (m *ActionLink) String() string { return proto.CompactTextString(m) } +func (*ActionLink) ProtoMessage() {} +func (*ActionLink) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{15} +} +func (m *ActionLink) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ActionLink.Unmarshal(m, b) +} +func (m *ActionLink) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ActionLink.Marshal(b, m, deterministic) +} +func (dst *ActionLink) XXX_Merge(src proto.Message) { + xxx_messageInfo_ActionLink.Merge(dst, src) +} +func (m *ActionLink) XXX_Size() int { + return xxx_messageInfo_ActionLink.Size(m) +} +func (m *ActionLink) XXX_DiscardUnknown() { + xxx_messageInfo_ActionLink.DiscardUnknown(m) +} + +var xxx_messageInfo_ActionLink proto.InternalMessageInfo + +func (m *ActionLink) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +func (m *ActionLink) GetLanguage() string { + if m != nil { + return m.Language + } + return "" +} + +func (m *ActionLink) GetRestrictedCountry() []string { + if m != nil { + return m.RestrictedCountry + } + return nil +} + +func (m *ActionLink) GetPlatform() ActionPlatform { + if m != nil { + return m.Platform + } + return ActionPlatform_ACTION_PLATFORM_UNSPECIFIED +} + +// TicketType is used to differentiate among tickets (where a ticket can be a +// spot on a raft trip, an admission to a museum, etc.) with different prices +// and/or availabilities due to different user types or different service +// attributes. +type TicketType struct { + // The ticket id is used to differentiate among different ticket types of the + // same service, and is only expected to be unique within a service. + TicketTypeId string `protobuf:"bytes,1,opt,name=ticket_type_id,json=ticketTypeId" json:"ticket_type_id,omitempty"` + // This can be user visible, e.g., “adult”, "child", “veteran”, “Row J”, etc. + ShortDescription string `protobuf:"bytes,2,opt,name=short_description,json=shortDescription" json:"short_description,omitempty"` + // The price of a single ticket of this type, exclusive of any taxes. The tax + // rate of Service is applied to its tickets. + Price *Price `protobuf:"bytes,3,opt,name=price" json:"price,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TicketType) Reset() { *m = TicketType{} } +func (m *TicketType) String() string { return proto.CompactTextString(m) } +func (*TicketType) ProtoMessage() {} +func (*TicketType) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{16} +} +func (m *TicketType) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TicketType.Unmarshal(m, b) +} +func (m *TicketType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TicketType.Marshal(b, m, deterministic) +} +func (dst *TicketType) XXX_Merge(src proto.Message) { + xxx_messageInfo_TicketType.Merge(dst, src) +} +func (m *TicketType) XXX_Size() int { + return xxx_messageInfo_TicketType.Size(m) +} +func (m *TicketType) XXX_DiscardUnknown() { + xxx_messageInfo_TicketType.DiscardUnknown(m) +} + +var xxx_messageInfo_TicketType proto.InternalMessageInfo + +func (m *TicketType) GetTicketTypeId() string { + if m != nil { + return m.TicketTypeId + } + return "" +} + +func (m *TicketType) GetShortDescription() string { + if m != nil { + return m.ShortDescription + } + return "" +} + +func (m *TicketType) GetPrice() *Price { + if m != nil { + return m.Price + } + return nil +} + +// Defines a media source. This field only contains the URL of this media source +// (only photos are supported for now). Google will crawl the media data to +// ensure that they are displayed to end-users in the most efficient way. +type RelatedMedia struct { + // URL of this media source. + Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"` + // Type of this media source. + Type RelatedMedia_MediaType `protobuf:"varint,2,opt,name=type,enum=maps.booking.feeds.RelatedMedia_MediaType" json:"type,omitempty"` + // Caption of the media, only plain text is supported. (optional) + Caption string `protobuf:"bytes,3,opt,name=caption" json:"caption,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RelatedMedia) Reset() { *m = RelatedMedia{} } +func (m *RelatedMedia) String() string { return proto.CompactTextString(m) } +func (*RelatedMedia) ProtoMessage() {} +func (*RelatedMedia) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{17} +} +func (m *RelatedMedia) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RelatedMedia.Unmarshal(m, b) +} +func (m *RelatedMedia) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RelatedMedia.Marshal(b, m, deterministic) +} +func (dst *RelatedMedia) XXX_Merge(src proto.Message) { + xxx_messageInfo_RelatedMedia.Merge(dst, src) +} +func (m *RelatedMedia) XXX_Size() int { + return xxx_messageInfo_RelatedMedia.Size(m) +} +func (m *RelatedMedia) XXX_DiscardUnknown() { + xxx_messageInfo_RelatedMedia.DiscardUnknown(m) +} + +var xxx_messageInfo_RelatedMedia proto.InternalMessageInfo + +func (m *RelatedMedia) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +func (m *RelatedMedia) GetType() RelatedMedia_MediaType { + if m != nil { + return m.Type + } + return RelatedMedia_TYPE_UNSPECIFIED +} + +func (m *RelatedMedia) GetCaption() string { + if m != nil { + return m.Caption + } + return "" +} + +// Identifies a particular value of a service attribute to be applied to a +// Service. +type ServiceAttributeValueId struct { + // ID of an attribute as defined in Merchant.service_attribute, e.g. + // "service-type". + AttributeId string `protobuf:"bytes,1,opt,name=attribute_id,json=attributeId" json:"attribute_id,omitempty"` + // ID of the value for this attribute, e.g. "haircut". Must match a value_id + // in the service attribute definition. + ValueId string `protobuf:"bytes,2,opt,name=value_id,json=valueId" json:"value_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServiceAttributeValueId) Reset() { *m = ServiceAttributeValueId{} } +func (m *ServiceAttributeValueId) String() string { return proto.CompactTextString(m) } +func (*ServiceAttributeValueId) ProtoMessage() {} +func (*ServiceAttributeValueId) Descriptor() ([]byte, []int) { + return fileDescriptor_feeds_0b3f05d3b8d0e7eb, []int{18} +} +func (m *ServiceAttributeValueId) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServiceAttributeValueId.Unmarshal(m, b) +} +func (m *ServiceAttributeValueId) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServiceAttributeValueId.Marshal(b, m, deterministic) +} +func (dst *ServiceAttributeValueId) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceAttributeValueId.Merge(dst, src) +} +func (m *ServiceAttributeValueId) XXX_Size() int { + return xxx_messageInfo_ServiceAttributeValueId.Size(m) +} +func (m *ServiceAttributeValueId) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceAttributeValueId.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceAttributeValueId proto.InternalMessageInfo + +func (m *ServiceAttributeValueId) GetAttributeId() string { + if m != nil { + return m.AttributeId + } + return "" +} + +func (m *ServiceAttributeValueId) GetValueId() string { + if m != nil { + return m.ValueId + } + return "" +} + +func init() { + proto.RegisterType((*FeedMetadata)(nil), "maps.booking.feeds.FeedMetadata") + proto.RegisterType((*AvailabilityFeed)(nil), "maps.booking.feeds.AvailabilityFeed") + proto.RegisterType((*ServiceAvailability)(nil), "maps.booking.feeds.ServiceAvailability") + proto.RegisterType((*Availability)(nil), "maps.booking.feeds.Availability") + proto.RegisterType((*Availability_Recurrence)(nil), "maps.booking.feeds.Availability.Recurrence") + proto.RegisterType((*Availability_ScheduleException)(nil), "maps.booking.feeds.Availability.ScheduleException") + proto.RegisterType((*Resources)(nil), "maps.booking.feeds.Resources") + proto.RegisterType((*TimeRange)(nil), "maps.booking.feeds.TimeRange") + proto.RegisterType((*ServiceFeed)(nil), "maps.booking.feeds.ServiceFeed") + proto.RegisterType((*Service)(nil), "maps.booking.feeds.Service") + proto.RegisterType((*Price)(nil), "maps.booking.feeds.Price") + proto.RegisterType((*SchedulingRules)(nil), "maps.booking.feeds.SchedulingRules") + proto.RegisterType((*TaxRate)(nil), "maps.booking.feeds.TaxRate") + proto.RegisterType((*ServiceIntakeFormField)(nil), "maps.booking.feeds.ServiceIntakeFormField") + proto.RegisterType((*ServiceIntakeForm)(nil), "maps.booking.feeds.ServiceIntakeForm") + proto.RegisterType((*Deposit)(nil), "maps.booking.feeds.Deposit") + proto.RegisterType((*NoShowFee)(nil), "maps.booking.feeds.NoShowFee") + proto.RegisterType((*ActionLink)(nil), "maps.booking.feeds.ActionLink") + proto.RegisterType((*TicketType)(nil), "maps.booking.feeds.TicketType") + proto.RegisterType((*RelatedMedia)(nil), "maps.booking.feeds.RelatedMedia") + proto.RegisterType((*ServiceAttributeValueId)(nil), "maps.booking.feeds.ServiceAttributeValueId") + proto.RegisterEnum("maps.booking.feeds.PriceType", PriceType_name, PriceType_value) + proto.RegisterEnum("maps.booking.feeds.RequireCreditCard", RequireCreditCard_name, RequireCreditCard_value) + proto.RegisterEnum("maps.booking.feeds.ActionPlatform", ActionPlatform_name, ActionPlatform_value) + proto.RegisterEnum("maps.booking.feeds.FeedMetadata_ProcessingInstruction", FeedMetadata_ProcessingInstruction_name, FeedMetadata_ProcessingInstruction_value) + proto.RegisterEnum("maps.booking.feeds.Service_PrepaymentType", Service_PrepaymentType_name, Service_PrepaymentType_value) + proto.RegisterEnum("maps.booking.feeds.Service_ServiceType", Service_ServiceType_name, Service_ServiceType_value) + proto.RegisterEnum("maps.booking.feeds.ServiceIntakeFormField_FieldType", ServiceIntakeFormField_FieldType_name, ServiceIntakeFormField_FieldType_value) + proto.RegisterEnum("maps.booking.feeds.RelatedMedia_MediaType", RelatedMedia_MediaType_name, RelatedMedia_MediaType_value) +} + +func init() { proto.RegisterFile("feeds.proto", fileDescriptor_feeds_0b3f05d3b8d0e7eb) } + +var fileDescriptor_feeds_0b3f05d3b8d0e7eb = []byte{ + // 2250 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x58, 0x4f, 0x73, 0xdb, 0xc6, + 0x15, 0x0f, 0x48, 0xd1, 0x24, 0x1f, 0x69, 0x19, 0x5c, 0xc9, 0x12, 0x2c, 0xc7, 0xb1, 0x0a, 0x37, + 0x53, 0x55, 0x4e, 0x99, 0x56, 0x4e, 0x32, 0xc9, 0xc4, 0x4d, 0x42, 0x81, 0x90, 0x85, 0x31, 0x45, + 0xc0, 0x4b, 0xc8, 0x8e, 0x7b, 0xd9, 0x40, 0xc0, 0x4a, 0xc2, 0x98, 0x04, 0xd8, 0x05, 0xa8, 0x58, + 0x99, 0xc9, 0xa5, 0xa7, 0x1e, 0x3b, 0xd3, 0x5b, 0x3f, 0x41, 0x3b, 0xf5, 0x07, 0xe8, 0xf4, 0xd6, + 0x53, 0x3f, 0x4e, 0x3f, 0x40, 0x2f, 0x9d, 0x5d, 0x80, 0x00, 0x28, 0x41, 0x96, 0x3b, 0xed, 0x85, + 0xc2, 0xbe, 0xf7, 0x7e, 0xfb, 0xe7, 0xbd, 0x7d, 0xbf, 0xf7, 0x56, 0xd0, 0x3a, 0xa6, 0xd4, 0x8b, + 0xba, 0x53, 0x16, 0xc6, 0x21, 0x42, 0x13, 0x67, 0x1a, 0x75, 0x8f, 0xc2, 0xf0, 0x95, 0x1f, 0x9c, + 0x74, 0x85, 0x46, 0xfd, 0x57, 0x05, 0xda, 0x7b, 0x94, 0x7a, 0x07, 0x34, 0x76, 0x3c, 0x27, 0x76, + 0xd0, 0x04, 0xd6, 0xa6, 0x2c, 0x74, 0x69, 0x14, 0xf9, 0xc1, 0x09, 0xf1, 0x83, 0x28, 0x66, 0x33, + 0x37, 0xf6, 0xc3, 0x40, 0x91, 0x36, 0xa5, 0xad, 0xe5, 0x9d, 0xcf, 0xba, 0x97, 0x67, 0xe9, 0x16, + 0x67, 0xe8, 0x5a, 0x19, 0xdc, 0xc8, 0xd1, 0xf8, 0xf6, 0xb4, 0x4c, 0x8c, 0x7e, 0x02, 0xed, 0xe8, + 0xd4, 0x61, 0x1e, 0x09, 0x66, 0x93, 0x23, 0xca, 0x94, 0xca, 0xa6, 0xb4, 0x55, 0xc3, 0x2d, 0x21, + 0x1b, 0x0a, 0x11, 0x37, 0x89, 0xc3, 0xd8, 0x19, 0x13, 0x21, 0x8c, 0x94, 0x6a, 0x62, 0x22, 0x64, + 0x23, 0x21, 0x42, 0xab, 0x50, 0x0b, 0xc2, 0xc0, 0xa5, 0x4a, 0x6d, 0x53, 0xda, 0x5a, 0xc2, 0xc9, + 0x00, 0xfd, 0x0a, 0x56, 0x4f, 0x68, 0x40, 0x99, 0xc3, 0x57, 0x22, 0xb1, 0x3f, 0xa1, 0x51, 0xec, + 0x4c, 0xa6, 0xca, 0xd2, 0xa6, 0xb4, 0x55, 0xc5, 0x2b, 0xb9, 0xce, 0x9e, 0xab, 0x54, 0x07, 0x6e, + 0x97, 0x6e, 0x1f, 0xad, 0xc0, 0x2d, 0x0b, 0x9b, 0x9a, 0x3e, 0x1a, 0x91, 0xc3, 0xe1, 0xd3, 0xa1, + 0xf9, 0x62, 0x28, 0xbf, 0x87, 0xd6, 0x61, 0x65, 0x2e, 0xec, 0x8d, 0x88, 0x66, 0x1e, 0x58, 0x03, + 0xdd, 0xd6, 0x65, 0x09, 0x6d, 0xc0, 0x5a, 0x41, 0x61, 0x0c, 0x35, 0xac, 0x1f, 0xe8, 0x43, 0xbb, + 0x37, 0x90, 0x2b, 0xea, 0x1b, 0x09, 0xe4, 0xde, 0x99, 0xe3, 0x8f, 0x9d, 0x23, 0x7f, 0xec, 0xc7, + 0xe7, 0xdc, 0x77, 0xe8, 0x31, 0x34, 0x26, 0xa9, 0xff, 0x84, 0x9f, 0x5b, 0x3b, 0x9b, 0xd7, 0xf9, + 0x19, 0x67, 0x08, 0xf4, 0x1b, 0x58, 0x8d, 0x28, 0x3b, 0xf3, 0x5d, 0x4a, 0x9c, 0xc2, 0xcc, 0x4a, + 0x65, 0xb3, 0xba, 0xd5, 0xda, 0xf9, 0x59, 0xd9, 0x4c, 0xa3, 0xc4, 0xbe, 0xb8, 0x11, 0xbc, 0x12, + 0x5d, 0x16, 0xaa, 0xff, 0xae, 0xc0, 0x4a, 0x89, 0x31, 0xfa, 0x1c, 0x94, 0x28, 0x76, 0x58, 0x9c, + 0xfb, 0x95, 0x30, 0x1a, 0xc5, 0xcc, 0x77, 0x63, 0x71, 0x82, 0x2a, 0x5e, 0x13, 0xfa, 0xcc, 0xb7, + 0x38, 0xd5, 0xa2, 0x4f, 0x60, 0x8d, 0x06, 0x5e, 0x19, 0xae, 0x22, 0x70, 0xab, 0x34, 0xf0, 0x2e, + 0xa3, 0x7e, 0x09, 0xab, 0x13, 0xca, 0xdc, 0x53, 0x27, 0x88, 0x89, 0xef, 0xe5, 0x18, 0x7e, 0x1b, + 0x9a, 0x18, 0xcd, 0x75, 0x86, 0x97, 0x21, 0xba, 0x30, 0x3f, 0xd0, 0x02, 0x60, 0x49, 0x00, 0x3a, + 0xa9, 0xaa, 0x60, 0x3f, 0x00, 0xc4, 0x68, 0x14, 0xce, 0x98, 0x4b, 0xa3, 0xdc, 0xfc, 0x86, 0x88, + 0xc6, 0xbd, 0x32, 0x1f, 0xe2, 0xb9, 0x35, 0xee, 0x64, 0xc0, 0x6c, 0xb6, 0x3e, 0xb4, 0x17, 0x62, + 0x51, 0x13, 0xb1, 0x28, 0x8d, 0xea, 0x42, 0x10, 0x16, 0x50, 0xea, 0x3f, 0x6b, 0xd0, 0x5e, 0x70, + 0xfb, 0x7d, 0x68, 0x15, 0xdc, 0x20, 0x3c, 0xdd, 0xc4, 0x90, 0x9f, 0x1e, 0xdd, 0x03, 0xc8, 0x4f, + 0x2d, 0x3c, 0xda, 0xc4, 0xcd, 0xec, 0xb0, 0xe8, 0x2e, 0x34, 0x93, 0xb0, 0x45, 0xd4, 0x15, 0xbe, + 0xab, 0xe2, 0x86, 0x10, 0x8c, 0xa8, 0xcb, 0x33, 0xcd, 0x9b, 0xa5, 0xe9, 0xc2, 0xf5, 0x49, 0xa2, + 0xb4, 0xe6, 0x32, 0x6e, 0x72, 0x1f, 0x5a, 0xd1, 0x34, 0x8c, 0x23, 0x22, 0xd2, 0x4f, 0xe4, 0x5b, + 0x15, 0x83, 0x10, 0xd9, 0x5c, 0x22, 0xd6, 0x17, 0x06, 0xe1, 0x94, 0x06, 0xc2, 0x7b, 0x55, 0xdc, + 0x14, 0x12, 0x73, 0x4a, 0x03, 0xf4, 0x73, 0x90, 0x8b, 0x07, 0x24, 0xb1, 0x73, 0xa2, 0xd4, 0xc5, + 0x26, 0x6f, 0x15, 0xe5, 0xb6, 0x73, 0x82, 0xbe, 0x84, 0x66, 0xe6, 0x56, 0xa5, 0xf1, 0x2e, 0x61, + 0xc8, 0xed, 0xd1, 0x36, 0x74, 0xa6, 0xce, 0xf9, 0x84, 0x06, 0x31, 0x09, 0xa7, 0xe2, 0x40, 0xbe, + 0xa7, 0x34, 0x37, 0xab, 0x7c, 0xa1, 0x54, 0x61, 0x0a, 0xb9, 0xe1, 0xa1, 0xa7, 0x00, 0x8c, 0xba, + 0x33, 0xc6, 0x28, 0xa7, 0x10, 0x10, 0x2b, 0x3d, 0xbc, 0x2e, 0x50, 0x5d, 0x9c, 0x41, 0x70, 0x01, + 0x8e, 0x1c, 0x40, 0x91, 0x7b, 0x4a, 0xbd, 0xd9, 0x98, 0x12, 0xfa, 0xda, 0xa5, 0x62, 0x11, 0xa5, + 0x25, 0xa2, 0xbf, 0x73, 0xed, 0xa4, 0xa3, 0x14, 0xaa, 0xcf, 0x91, 0xb8, 0x13, 0x5d, 0x14, 0x6d, + 0x7c, 0x07, 0x90, 0x2f, 0x8e, 0xb6, 0x40, 0x66, 0x74, 0x4a, 0x9d, 0x98, 0xcc, 0x82, 0xd8, 0x1f, + 0x8b, 0xc0, 0x25, 0x09, 0xb8, 0x9c, 0xc8, 0x0f, 0xb9, 0x98, 0xc7, 0x2e, 0xb7, 0xa4, 0x67, 0x94, + 0x9d, 0x0b, 0xcb, 0x84, 0x6f, 0x53, 0x4b, 0x9d, 0x8b, 0x47, 0xd4, 0xdd, 0x78, 0x06, 0x9d, 0x4b, + 0x3b, 0x41, 0x8f, 0x01, 0x78, 0xce, 0x12, 0xe6, 0x04, 0x27, 0x34, 0x65, 0xa9, 0xd2, 0x80, 0xf0, + 0xe4, 0xc5, 0xdc, 0x08, 0x37, 0xe3, 0xf9, 0xa7, 0xfa, 0x27, 0x09, 0x9a, 0x59, 0xa4, 0xd0, 0x1d, + 0xe0, 0xb7, 0xee, 0xf8, 0x38, 0xbf, 0xc3, 0x75, 0x31, 0x4e, 0x2f, 0xb0, 0x50, 0x05, 0xce, 0x84, + 0x66, 0x17, 0x98, 0x4b, 0x86, 0xce, 0x84, 0xa2, 0x75, 0xa8, 0xb3, 0x30, 0x9c, 0x70, 0x60, 0x92, + 0xfa, 0x37, 0xf8, 0x30, 0xb9, 0xd9, 0x42, 0x21, 0x60, 0x49, 0x92, 0x37, 0xb8, 0x40, 0xa0, 0xee, + 0x01, 0x4c, 0x1d, 0x16, 0x9f, 0x93, 0xc8, 0xff, 0x21, 0xa9, 0x12, 0x35, 0xdc, 0x14, 0x92, 0x91, + 0xff, 0x03, 0x55, 0x7b, 0xd0, 0xcc, 0x36, 0xcd, 0x27, 0x3a, 0xa2, 0x27, 0x7e, 0x50, 0xf0, 0x64, + 0x43, 0x08, 0xb8, 0x0f, 0xd7, 0xa1, 0xce, 0xc9, 0x6b, 0xee, 0xba, 0x2a, 0xbe, 0x41, 0x03, 0x6f, + 0x44, 0x5d, 0xf5, 0x77, 0x12, 0xb4, 0x52, 0x9e, 0xfc, 0x3f, 0x30, 0xfa, 0xa7, 0x50, 0x4f, 0x73, + 0x36, 0x25, 0xf1, 0xbb, 0x6f, 0x21, 0x71, 0x3c, 0xb7, 0x55, 0xdf, 0x00, 0xd4, 0x53, 0xe1, 0xff, + 0xcc, 0x14, 0x08, 0x96, 0x84, 0x2b, 0x13, 0x2f, 0x8b, 0x6f, 0xb4, 0x09, 0x2d, 0x8f, 0x46, 0x2e, + 0xf3, 0x93, 0x5b, 0x9d, 0x78, 0xb9, 0x28, 0x42, 0x1f, 0x43, 0x6d, 0xca, 0xfc, 0xb4, 0x12, 0xb7, + 0x76, 0xee, 0x94, 0x6d, 0xdb, 0xe2, 0x06, 0x38, 0xb1, 0x43, 0x5f, 0x40, 0x8d, 0xcd, 0xc6, 0x34, + 0x4a, 0x89, 0xf6, 0x41, 0xe9, 0x39, 0x93, 0xbb, 0xe8, 0x07, 0x27, 0x98, 0x9b, 0xe2, 0x04, 0x81, + 0xbe, 0x80, 0xa5, 0xe3, 0x90, 0x4d, 0x94, 0xba, 0xf0, 0xd0, 0x87, 0x6f, 0xf1, 0x90, 0x11, 0xc4, + 0xce, 0x2b, 0xba, 0x17, 0xb2, 0x09, 0x16, 0x10, 0x34, 0x82, 0x5b, 0x53, 0x46, 0xe7, 0x0c, 0x11, + 0x9f, 0x4f, 0xa9, 0x60, 0x98, 0xe5, 0x9d, 0xed, 0xb7, 0xcc, 0xd2, 0xb5, 0x32, 0x88, 0x7d, 0x3e, + 0xa5, 0x78, 0x79, 0xba, 0x30, 0x46, 0x9f, 0x41, 0x23, 0x76, 0x5e, 0x13, 0xe6, 0xc4, 0x54, 0x69, + 0x8a, 0xd3, 0x94, 0x46, 0xcd, 0x76, 0x5e, 0x63, 0x27, 0xa6, 0xb8, 0x1e, 0x27, 0x1f, 0xe5, 0x5c, + 0x05, 0xe5, 0x5c, 0xf5, 0x29, 0xd4, 0x3d, 0x3a, 0x0d, 0x23, 0x3f, 0x56, 0x5a, 0x57, 0x2f, 0xd1, + 0x4f, 0x4c, 0xf0, 0xdc, 0x16, 0xfd, 0x1a, 0x5a, 0x41, 0x48, 0xa2, 0xd3, 0xf0, 0x7b, 0x72, 0x4c, + 0xa9, 0xd2, 0xbe, 0x3a, 0x79, 0x87, 0xe1, 0xe8, 0x34, 0xfc, 0x7e, 0x8f, 0x52, 0xdc, 0x0c, 0xe6, + 0x9f, 0xe8, 0x10, 0x56, 0x18, 0xfd, 0xed, 0xcc, 0x67, 0x94, 0xb8, 0x8c, 0x7a, 0x7e, 0x4c, 0x5c, + 0x87, 0x79, 0xca, 0x4d, 0xe1, 0xb2, 0x0f, 0xcb, 0x49, 0x59, 0x98, 0x6b, 0xc2, 0x5a, 0x73, 0x98, + 0xc7, 0x6b, 0xe4, 0x05, 0x11, 0xfa, 0x1a, 0x5a, 0x8e, 0x68, 0xaf, 0xc8, 0xd8, 0x0f, 0x5e, 0x29, + 0xcb, 0x22, 0x8e, 0x1f, 0x94, 0x92, 0xa4, 0x30, 0x1b, 0xf8, 0xc1, 0x2b, 0x0c, 0x4e, 0xf6, 0x8d, + 0xbe, 0x84, 0x25, 0x11, 0xbb, 0x5b, 0x62, 0x23, 0x6f, 0x6b, 0x74, 0xe6, 0x7f, 0x45, 0xe0, 0x04, + 0x88, 0xaf, 0x1e, 0xfb, 0xee, 0x2b, 0x9a, 0xc6, 0x5f, 0xbe, 0x7a, 0x75, 0x5b, 0x98, 0x09, 0x28, + 0xc4, 0xd9, 0x37, 0xd2, 0xe1, 0x26, 0xa3, 0x63, 0x27, 0xa6, 0x1e, 0x99, 0x50, 0xcf, 0x77, 0x94, + 0xce, 0xd5, 0x35, 0x1e, 0x27, 0x86, 0x07, 0xdc, 0x0e, 0xb7, 0x59, 0x61, 0x84, 0x4e, 0x61, 0x23, + 0xeb, 0xde, 0xe2, 0x98, 0xf9, 0x47, 0xb3, 0x98, 0x92, 0x33, 0x67, 0x3c, 0x13, 0x79, 0x89, 0xc4, + 0x9c, 0x0f, 0xdf, 0xd6, 0xc3, 0xcd, 0x41, 0xcf, 0x39, 0xc6, 0xf0, 0xf0, 0x7a, 0x54, 0xae, 0x50, + 0xbf, 0x83, 0xe5, 0xc5, 0x2b, 0x8c, 0xee, 0xc3, 0x5d, 0x0b, 0xeb, 0x56, 0xef, 0x25, 0x6f, 0x4f, + 0x89, 0xfd, 0xd2, 0xd2, 0xc9, 0xe1, 0x70, 0x64, 0xe9, 0x9a, 0xb1, 0x67, 0xe8, 0x7d, 0xf9, 0x3d, + 0xd4, 0x86, 0x06, 0xd6, 0x9f, 0x1d, 0x1a, 0x58, 0xef, 0xcb, 0x12, 0x1f, 0x99, 0x96, 0x6d, 0x98, + 0x43, 0xde, 0xc9, 0xa2, 0x0e, 0xdc, 0x1c, 0x9a, 0x36, 0x19, 0x1d, 0x5a, 0x96, 0x89, 0x6d, 0xbd, + 0x2f, 0x57, 0xd5, 0xbf, 0xe6, 0x2c, 0x28, 0xe6, 0x7f, 0x1f, 0x94, 0x91, 0x8e, 0x9f, 0x1b, 0x9a, + 0x5e, 0x36, 0xf9, 0x03, 0xb8, 0xbf, 0xa0, 0xed, 0x1b, 0x43, 0x63, 0xf8, 0x84, 0x60, 0x9d, 0x4b, + 0x7b, 0x7c, 0x1d, 0x59, 0x42, 0x1f, 0xc0, 0xc6, 0x82, 0xd1, 0x9e, 0x69, 0xf6, 0x89, 0x89, 0xfb, + 0x3a, 0x36, 0x86, 0x4f, 0xe4, 0x0a, 0xba, 0x07, 0x77, 0x16, 0xf4, 0xfa, 0x73, 0x71, 0x14, 0x43, + 0x7b, 0xaa, 0xdb, 0x72, 0x95, 0xb7, 0xe2, 0x0b, 0x6a, 0x1b, 0x1b, 0x16, 0xb1, 0xcd, 0x43, 0x2c, + 0x2f, 0xa9, 0x3f, 0x42, 0x4d, 0x70, 0x11, 0x6f, 0x7c, 0x04, 0x1b, 0x91, 0x89, 0xef, 0xb2, 0x30, + 0x4a, 0x59, 0xbf, 0x25, 0x64, 0x07, 0x42, 0x84, 0x1e, 0xc0, 0xcd, 0xb4, 0xe4, 0x9e, 0x13, 0x37, + 0xf4, 0xe6, 0x95, 0xa9, 0x3d, 0x17, 0x6a, 0xa1, 0x47, 0xd1, 0x47, 0x80, 0x38, 0x86, 0xbf, 0x9c, + 0xd2, 0x4c, 0xe6, 0xfd, 0x4d, 0xc2, 0xa0, 0x72, 0xaa, 0x49, 0x52, 0xd9, 0x76, 0x4e, 0xd4, 0x3f, + 0x56, 0xe0, 0xd6, 0x05, 0x6a, 0xe3, 0x4d, 0xeb, 0xc4, 0x0f, 0x88, 0xe3, 0x9d, 0x39, 0x81, 0x4b, + 0x49, 0x1a, 0xf0, 0x74, 0x43, 0x9d, 0x89, 0x1f, 0xf4, 0x12, 0xcd, 0x6e, 0xa2, 0x40, 0x5f, 0xc3, + 0xfb, 0x45, 0xfb, 0x30, 0x18, 0xfb, 0x01, 0x25, 0x2e, 0x1f, 0xf0, 0x49, 0xd3, 0x22, 0x75, 0x27, + 0x07, 0x9a, 0xc2, 0x42, 0x9b, 0x1b, 0xa0, 0x67, 0x70, 0x9b, 0xdf, 0xc5, 0x14, 0x32, 0x4e, 0x9a, + 0x3f, 0xce, 0x11, 0xd5, 0x6b, 0x08, 0x7c, 0xb7, 0xa2, 0x48, 0x78, 0x85, 0x63, 0xb5, 0x02, 0x94, + 0xb3, 0xc5, 0x63, 0x80, 0x20, 0xcc, 0xb8, 0x66, 0xe9, 0x5d, 0xe6, 0x69, 0x26, 0x80, 0x3d, 0x4a, + 0xd5, 0x2e, 0xd4, 0x53, 0x86, 0xe4, 0x3e, 0x17, 0x01, 0x21, 0x53, 0xca, 0x5c, 0x1a, 0x24, 0x0f, + 0x8b, 0x1a, 0x6e, 0x0b, 0xa1, 0x95, 0xc8, 0xd4, 0xbf, 0x57, 0x60, 0xed, 0x12, 0xcd, 0xef, 0xf9, + 0x74, 0xec, 0xa1, 0xfd, 0x94, 0x1e, 0x92, 0x97, 0xeb, 0x27, 0xef, 0x54, 0x20, 0x04, 0xb2, 0x2b, + 0x7e, 0x0b, 0x5c, 0xb1, 0x0a, 0xb5, 0xb1, 0x73, 0x44, 0xc7, 0x69, 0xd4, 0x93, 0x01, 0x97, 0x8a, + 0x3c, 0x55, 0xaa, 0x82, 0xac, 0x93, 0x01, 0x2f, 0xbc, 0x3e, 0x7f, 0x40, 0x08, 0xb6, 0xf3, 0xc4, + 0xf9, 0x1b, 0x18, 0xfc, 0x28, 0xa5, 0x44, 0x4f, 0xfd, 0xbd, 0x04, 0xcd, 0x6c, 0x01, 0x7e, 0x41, + 0xf7, 0x0c, 0x7d, 0xd0, 0x2f, 0x4b, 0x10, 0x19, 0xda, 0xa3, 0x7d, 0x13, 0xdb, 0xa4, 0x37, 0x1c, + 0xbd, 0xd0, 0xb1, 0x2c, 0xa1, 0x9b, 0xd0, 0xb4, 0x7a, 0xb8, 0xf7, 0x04, 0xf7, 0xac, 0x7d, 0xb9, + 0xc2, 0x9f, 0xa5, 0x07, 0x87, 0x03, 0xdb, 0xb0, 0x06, 0x3a, 0xd1, 0xf6, 0x4d, 0x43, 0xd3, 0xe5, + 0x2a, 0x5a, 0x06, 0xd0, 0xf6, 0x75, 0xed, 0xe9, 0xae, 0xf9, 0xad, 0x3e, 0x92, 0x97, 0x78, 0xd6, + 0xf6, 0xb1, 0x69, 0xf5, 0xf9, 0xa3, 0xb5, 0x86, 0x5a, 0x50, 0xdf, 0x35, 0xcd, 0x81, 0xde, 0x1b, + 0xca, 0x37, 0xd4, 0xbf, 0x49, 0xd0, 0xb9, 0xe4, 0x02, 0xf4, 0x0d, 0xd4, 0x8e, 0xf9, 0xfe, 0x14, + 0x49, 0x90, 0xcf, 0xf6, 0xbb, 0x3b, 0x0e, 0x27, 0x40, 0xfe, 0x5a, 0x3b, 0xf6, 0x59, 0x94, 0xbc, + 0x0e, 0x89, 0x3b, 0x8b, 0xe2, 0x70, 0x42, 0x59, 0x24, 0xdc, 0xd7, 0xc0, 0x48, 0xe8, 0x78, 0xc7, + 0xa5, 0xcd, 0x35, 0xe8, 0x63, 0x5e, 0x62, 0xe2, 0x19, 0x0b, 0x78, 0xf2, 0xe4, 0x80, 0x6a, 0x02, + 0xc8, 0x54, 0x19, 0x40, 0xfd, 0x87, 0x04, 0xf5, 0xb4, 0xce, 0xa1, 0x47, 0x79, 0x55, 0x94, 0xae, + 0xeb, 0x3b, 0xb2, 0x9a, 0x78, 0x21, 0x75, 0x16, 0x12, 0x20, 0xef, 0xef, 0x0a, 0xa9, 0x53, 0xbc, + 0xe7, 0xbc, 0x17, 0xfc, 0x06, 0xda, 0xe9, 0x5c, 0x49, 0x05, 0xa9, 0x8a, 0x6b, 0x76, 0xef, 0xca, + 0xa5, 0xc5, 0x7d, 0x6a, 0xa5, 0x10, 0x3e, 0x50, 0x19, 0x34, 0xb3, 0x7a, 0x8b, 0x1e, 0x42, 0x95, + 0xe7, 0xcb, 0xb5, 0x07, 0xe0, 0x56, 0xe8, 0x73, 0x68, 0x1c, 0x53, 0xfa, 0x5f, 0xac, 0x5b, 0x3f, + 0xa6, 0xe2, 0x43, 0xfd, 0x8b, 0x04, 0x90, 0x97, 0x53, 0x24, 0x43, 0x75, 0xc6, 0xc6, 0x69, 0x7b, + 0xc8, 0x3f, 0xd1, 0x06, 0x34, 0xc6, 0x4e, 0x70, 0x32, 0x73, 0x4e, 0xe6, 0x24, 0x97, 0x8d, 0xd1, + 0x2f, 0xc4, 0x1b, 0x59, 0xbc, 0x70, 0xa9, 0x47, 0xdc, 0x70, 0x16, 0xc4, 0xec, 0x3c, 0xbd, 0xfe, + 0x9d, 0x5c, 0xa3, 0x25, 0x0a, 0xf4, 0x15, 0x34, 0xa6, 0x63, 0x27, 0x16, 0x5d, 0xda, 0x92, 0xd8, + 0xa5, 0x7a, 0x75, 0x75, 0xb7, 0x52, 0x4b, 0x9c, 0x61, 0xd4, 0x3f, 0x48, 0x00, 0x79, 0xf1, 0x45, + 0x3f, 0x85, 0xe5, 0x42, 0xc5, 0xce, 0xbb, 0xda, 0x76, 0x5e, 0x94, 0x0d, 0x0f, 0x3d, 0x84, 0x4e, + 0x74, 0x1a, 0xb2, 0x98, 0x14, 0x5b, 0xd5, 0xe4, 0x20, 0xb2, 0x50, 0xf4, 0xcb, 0xfa, 0xd5, 0xea, + 0xbb, 0xf5, 0xab, 0xea, 0x9f, 0x25, 0x68, 0x17, 0x8b, 0x79, 0x89, 0x03, 0xbf, 0x4a, 0x69, 0xa7, + 0x72, 0x75, 0x47, 0x59, 0x9c, 0xa1, 0x2b, 0x7e, 0x0b, 0x64, 0xa3, 0x40, 0xdd, 0x75, 0x92, 0x6d, + 0x27, 0xa5, 0x63, 0x3e, 0x54, 0x3f, 0x82, 0x66, 0x66, 0x8c, 0x56, 0x41, 0x2e, 0xa1, 0x8c, 0x26, + 0xd4, 0xac, 0x7d, 0xd3, 0x36, 0x65, 0x49, 0x7d, 0x01, 0xeb, 0x57, 0xb4, 0x08, 0xbc, 0xe0, 0xe5, + 0xbd, 0x46, 0xe6, 0xc7, 0x56, 0x26, 0x33, 0x3c, 0xfe, 0x44, 0xcb, 0x9a, 0x90, 0xc4, 0x7b, 0xf5, + 0xb3, 0x04, 0xbd, 0xfd, 0x08, 0x9a, 0xd9, 0xc5, 0x42, 0x6b, 0x80, 0xf6, 0x8c, 0x6f, 0xf5, 0x3e, + 0xc1, 0x3d, 0x5b, 0x27, 0x7d, 0x7d, 0xaf, 0x77, 0x38, 0xb0, 0xe5, 0xf7, 0x38, 0x0b, 0x59, 0x3a, + 0x26, 0x96, 0x8e, 0x47, 0xbc, 0x8e, 0x6f, 0xff, 0x08, 0x9d, 0x4b, 0x4d, 0x21, 0xef, 0x00, 0xd2, + 0xf6, 0x82, 0x68, 0x58, 0xef, 0x1b, 0x36, 0xd1, 0x7a, 0xb8, 0x7f, 0xb9, 0x4d, 0x28, 0x33, 0xd2, + 0xcc, 0x61, 0xdf, 0x48, 0x9b, 0x11, 0xd1, 0x26, 0x94, 0x19, 0xf5, 0x06, 0x2f, 0x7a, 0x2f, 0x47, + 0x72, 0x65, 0xfb, 0x8d, 0x04, 0xcb, 0x8b, 0xf7, 0x8c, 0x37, 0x3f, 0x3d, 0x8d, 0x4f, 0x40, 0xac, + 0x41, 0xcf, 0xde, 0x33, 0xf1, 0xc1, 0xe5, 0x85, 0x2f, 0x1a, 0xbc, 0xd0, 0x77, 0x49, 0xcf, 0xb2, + 0x06, 0x86, 0x56, 0xe8, 0x4f, 0x2e, 0x1a, 0x1d, 0x98, 0xbb, 0xc6, 0x40, 0xe7, 0xb6, 0x72, 0x05, + 0xdd, 0x85, 0xf5, 0x8b, 0xfa, 0xde, 0xb0, 0x8f, 0x4d, 0xa3, 0x2f, 0x57, 0xd1, 0x3a, 0xac, 0x5c, + 0x54, 0x1a, 0xe6, 0x48, 0x5e, 0x3a, 0xba, 0x21, 0xfe, 0x65, 0xfb, 0xe8, 0x3f, 0x01, 0x00, 0x00, + 0xff, 0xff, 0xcc, 0x96, 0x13, 0xc4, 0xc1, 0x15, 0x00, 0x00, +}
diff --git a/feeds/feeds.proto b/feeds/feeds.proto new file mode 100644 index 0000000..55e946e --- /dev/null +++ b/feeds/feeds.proto
@@ -0,0 +1,658 @@ +syntax = "proto3"; + +package maps.booking.feeds; + +message FeedMetadata { + enum ProcessingInstruction { + // By default we will assume that this feed is an incremental feed. + PROCESS_UNKNOWN = 0; + + // This Feed message is one shard of a complete feed. Anything previously + // supplied by this partner will be deleted; the contents of this feed + // represent the entire state of the world. + PROCESS_AS_COMPLETE = 1; + + // This Feed message is one shard of an incremental feed. Existing entities + // will be left untouched except as modified in this feed. + PROCESS_AS_INCREMENTAL = 2; + } + + // Instructs us how to process the feed: either as a shard of a complete feed, + // or as a shard of an incremental update. + ProcessingInstruction processing_instruction = 1; + + // The current shard and total number of shards for this feed. + // + // Shard number is assumed to be zero-based. + // + // There does not need to be any relationship to the file name. + // + // Shards do not need to be transferred in order, and they may not be + // processed in order. + int32 shard_number = 2; + int32 total_shards = 3; + + // An identifier that must be consistent across all shards in a feed. + // This value must be globally unique across each feed type. + // + // This value ensures that complete feeds spanning multiple shards are + // processed together correctly. + // + // Clients only need to set this value when the processing_instruction is set + // to PROCESS_AS_COMPLETE and the feed spans multiple shards (defined by + // total_shards). + // + // Feeds that span multiple shards must set this nonce to the same value. + uint64 nonce = 5; + + // The timestamp at which this feed shard was generated. + // + // In Unix time format (seconds since the epoch). + int64 generation_timestamp = 4; +} + +message AvailabilityFeed { + FeedMetadata metadata = 1; + repeated ServiceAvailability service_availability = 2; +} + +message ServiceAvailability { + // If provided, we will consider the Availability entities provided to be a + // complete snapshot from [start_timestamp_restrict, end_timestamp_restrict). + // That is, all existing availability will be deleted if the following + // condition holds true: + // + // start_timestamp_restrict <= Availability.start_sec && + // Availability.start_sec < end_timestamp_restrict + // + // If a resource_restrict message is set, the condition is further restricted: + // + // Availability.resource.staff_id == resource_restrict.staff_id && + // Availability.resource.room_id == resource_restrict.room_id + // + // These fields are typically used to provide a complete update of + // availability in a given time range. + // + // Setting start_timestamp_restrict while leaving end_timestamp_restrict unset + // is interpreted to mean all time beginning at start_timestamp_restrict. + // + // Setting end_timestamp_restrict while leaving start_timestamp_restrict unset + // is interpreted to mean all time up to the end_timestamp_restrict. + // + // In Unix time format (seconds since the epoch). + int64 start_timestamp_restrict = 1; + int64 end_timestamp_restrict = 2; + + // If provided, the timestamp restricts will be applied only to the given + // merchant or service. + // + // These fields are typically used to provide complete snapshot of + // availability in a given range (defined above) for a specific merchant or + // service. + // + // Leaving these fields unset, or setting these to the empty string or null, + // is interpreted to mean that no restrict is intended. + string merchant_id_restrict = 3; + string service_id_restrict = 4; + + // Setting resources_restrict further restricts the scope of the update to + // just this set of resources. All id fields of the resources must match + // exactly. + Resources resources_restrict = 6; + + repeated Availability availability = 5; +} + +// An availability of the merchant's service, indicating time and number +// of spots. +// The availability feed should be a list of this message. +// Please note that it's up to the partner to call out all the possible +// availabilities. +// If a massage therapist is available 9am-12pm, and they provide +// one-hour massage sessions, the aggregator should provide the feed as +// availability {start_sec: 9am, duration: 60 minutes, ...} +// availability {start_sec: 10am, duration: 60 minutes, ...} +// availability {start_sec: 11am, duration: 60 minutes, ...} +// instead of +// availability {start_sec: 9am, duration: 180 minutes, ...} +// +message Availability { + // An opaque string from an aggregator to identify a merchant. + string merchant_id = 1; + // An opaque string from aggregator to identify a service of the + // merchant. + string service_id = 2; + // Start time of this availability, using epoch time in seconds. + int64 start_sec = 3; + // Duration of the service in seconds, e.g. 30 minutes for a chair massage. + int64 duration_sec = 4; + // Number of total spots and open spots of this availability. + // E.g. a Yoga class of 10 spots with 3 booked. + // availability {spots_total: 10, spots_open: 7 ...} + // E.g. a chair massage session which was already booked. + // availability {spots_total: 1, spots_open: 0 ...} + // + // Note: If sending requests using the availability compression format defined + // below, these two fields will be inferred. A Recurrence + // implies spots_total=1 and spots_open=1. A ScheduleException implies + // spots_total=1 and spots_open=0. + int64 spots_total = 5; + int64 spots_open = 6; + // An optional opaque string to identify this availability slot. If set, it + // will be included in the requests that book/update/cancel appointments. + string availability_tag = 7; + + // Optional resources used to disambiguate this availability slot from + // others when different staff, room, or party_size values are part + // of the service. + // + // E.g. the same Yoga class with two 2 instructors. + // availability { resources { staff_id: "1" staff_name: "Amy" } + // spots_total: 10 spots_open: 7 } + // availability { resources { staff_id: "2" staff_name: "John" } + // spots_total: 5 spots_open: 2 } + Resources resources = 8; + + // A list of ids referencing the payment options which can be used to pay + // for this slot. The actual payment options are defined at the Merchant + // level, and can also be shared among multiple Merchants. + // + // This field overrides any payment_option_ids specified in the service + // message. Similarly payment_option_ids specified here do NOT have to be + // present in the service message, though must be defined at the + // Merchant level. + repeated string payment_option_id = 9; + + // Recurrence messages are optional, but allow for a more compact + // representation of consistently repeating availability slots. They typically + // represent a day's working schedule. + // ScheduleException messages are then used to represent booked/unavailable + // time ranges within the work day. + // + // Requirements: + // 1. The expansion of availability slots or recurrences must NOT create + // identical slots. If the ids, start_sec, duration_sec, and resources + // match, slots are considered identical. + // 2. Do NOT mix the standard availability format and recurrence within the + // slots of a single service. Recurrence benefits merchants/services that + // offer appointments. The standard format is geared towards + // merchants/services with regularly scheduled classes. + message Recurrence { + // The inclusive maximum UTC timestamp the availability repeats until. + int64 repeat_until_sec = 1; + // Defines the time between successive availability slots. + // + // E.g. An availability with a duration of 20 min, a repeat_every_sec of + // 30 min, a start_sec of 9:00am, and a repeat_until_sec of 11:00am will + // yield slots at 9-9:20am, 9:30-9:50am, 10-10:20am, 10:30-10:50am, + // 11-11:20am. + int32 repeat_every_sec = 2; + } + // The recurrence information for the availability, representing more than one + // start time. A recurrence should contain appointments for one working day. + Recurrence recurrence = 10; + + // ScheduleException messages are used to represent booked/unavailable time + // ranges within the work day. As time slots are booked, the list of + // exceptions should grow to reflect the newly unavailable time ranges. + // The recurrence itself shouldn't be modified. + message ScheduleException { + // The time range of the exception. + TimeRange time_range = 1; + } + // When this service cannot be scheduled. To limit the number of + // schedule_exception messages consider joining adjacent exceptions. + repeated ScheduleException schedule_exception = 11; +} + +// A resource is used to disambiguate availability slots from one another when +// different staff, room or party_size values are part of the service. +// Multiple slots for the same service and time interval can co-exist when they +// have different resources. +message Resources { + // Optional id for a staff member providing the service. This field identifies + // the staff member across all merchants, services, and availability records. + // It also needs to be stable over time to allow correlation with past + // bookings. + // This field must be present if staff_name is present. + string staff_id = 1; + + // Optional name of a staff member providing the service. This field will be + // displayed to users making a booking, and should be human readable, as + // opposed to an opaque identifier. + // This field must be present if staff_id is present. + string staff_name = 2; + + // An optional id for the room the service is located in. This field + // identifies the room across all merchants, services, and availability + // records. It also needs to be stable over time to allow correlation with + // past bookings. + // This field must be present if room_name is present. + string room_id = 3; + + // An optional name for the room the service is located in. This + // field will be displayed to users making a booking, and should be human + // readable, as opposed to an opaque identifier. + // This field must be present if room_id is present. + string room_name = 4; + + // Applicable only for Dining: The party size which can be accommodated + // during this time slot. A restaurant can be associated with multiple Slots + // for the same time, each specifying a different party_size, if for instance + // 2, 3, or 4 people can be seated with a reservation. + int32 party_size = 5; +} + +message TimeRange { + int64 begin_sec = 1; + int64 end_sec = 2; +} + +message ServiceFeed { + FeedMetadata metadata = 1; + repeated Service service = 2; +} + +// The definition of a service provided by a merchant. +message Service { + // An opaque string from an aggregator partner which uniquely identifies a + // merchant. (required) + string merchant_id = 1; + // An opaque string from an aggregator partner which uniquely identifies the + // service. (required) + string service_id = 2; + // The name of the service, suitable for display to users, e.g. "Men's + // haircut". (required) + string name = 3; + // The description of the service, suitable for display to users. + // + // This field now supports both plain text and HTML-like formatting rules to + // display structural contents to end-users. Unlike plain text sections, + // customized layouts can be created here using headings, paragraphs, lists + // and some phrase tags. Please read the following instructions and notes + // carefully to create better user-experience. + // + // Supported HTML-like formatting tags: + // + // Heading tags: <h1>, <h2>, <h3>, <h4>, <h5>, <h6> + // Heading tags can be used to display titles and sub-titles. For example, + // <h1>Itinerary</h1> will display the inline text as the most important + // heading of the section. Note that any inner HTML tags, styles or + // attributes will be ignored. For example, <h1 style=".."> will be treated + // the same as <h1>. Only pure text wil be preserved. + // + // Paragraph tag: <p>: + // Paragraph tag can be used to wrap detailed introduction or contents. Any + // inner tags, styles or attributes will be ignored with a few exceptions: + // <br>, <strong> and <em>. Please see the phrase tag section below for more + // details. + // + // List tags: <ul>, <ol>, <li> + // <ul> tag can be used with <li> tag to display unordered lists, and <ol> + // tag can be used with <li> to display ordered lists. This is a good tool + // to display checklists, highlights or any other lists that fit your + // use-cases. + // Example: To show a list of features of a cruise trip: + // <ol> + // <li>Wonderful ocean view and chances to play with wildlife.</li> + // <li>Carefully designed travel arrangements and services.</li> + // <li>Gauranteed lowest price.</li> + // </ol> + // Note that only <li> children under <ul> or <ol> tags will be converted. All + // other children will be dropped. Also any inner tags, attributes and styles + // will be ignored, only pure text contents are preserved. + // + // Division tag: <div> + // All supported inner tags of the <div> tag will be parsed with the rules + // stated above. But <div> tag itself does not mean any grouping or + // indenting here. Also any inner attributes and styles will be ignored. + // + // Phrase tags: <br>, <strong>, <em>: + // Only the three tags mentioned above are supported. <br> can be used to + // break lines in paragraphs, and <strong>/<em> can be used to highlight + // important text. Any other phrase tags will be ignored. + // + // Unsupported tags: + // * <html>, <header> and <body> tags are not allowed. + // * Any URLs, anchors, links will be striped, and will never be displayed + // to end-users. If you want to use photos to create a rich user experience, + // please use the "related_media" field below to send your photo urls + // * Any other tags not mentioned above are not supported (for example + // <table>, <td> ...). + // + // Important notes: + // * Try not to use other tags except for the supported ones mentioned + // above, because contents within unsupported tags will be excluded from UI + // rendering and may lead to undesired user experience. + // * Try avoid deep nested structures like more than 3 different heading + // levels or nested lists. Keeping the structure flat, simple and + // straightforward helps to create a better user experience. + // * If the currently supported layouts are not enough for your use cases, + // please contact Google with your requests and we will find the best + // approach for you. + // + // (required) + string description = 4; + // The price of the service. (optional, overridden when payment options or + // ticket types present) + Price price = 5; + // Rules to book/cancel an appointment. (optional) + SchedulingRules rules = 6; + // Intake forms to customize the service. (optional) + repeated ServiceIntakeForm form = 7; + + // Enum to indicate the prepayment type. + enum PrepaymentType { + // By default we will assume that the prepayment is NOT_SUPPORTED. + PREPAYMENT_TYPE_UNSPECIFIED = 0; + // The user has to pay this service at the booking time. + REQUIRED = 1; + // The user can choose to pre-pay this service at the booking time or later, + // but it is not required in order to book. + OPTIONAL = 2; + // The prepayment is not supported for this service. + NOT_SUPPORTED = 3; + } + // Whether a prepayment is required, optional or not supported. (optional) + PrepaymentType prepayment_type = 8; + + // The service's tax rate. If present this field overrides any tax_rate set at + // the merchant level. An empty message (i.e. tax_rate { }) will reset the + // applied tax rate to zero. (optional) + TaxRate tax_rate = 9; + + // A list of ids referencing the payment options which can be used to pay + // for this service. The actual payment options are defined at the Merchant + // level, and can also be shared among multiple Merchants. (optional) + repeated string payment_option_id = 10; + + // Defines how a deposit may be charged to the user. Can be overridden at the + // availability level. (optional) + Deposit deposit = 11; + + // Defines a no show fee that may be charged to the user. Can be overridden + // at the availability level. (optional) + NoShowFee no_show_fee = 12; + + // Indicates whether the user must provide a credit card in order to book this + // service. + // This value can be overridden at the availability level. (optional) + RequireCreditCard require_credit_card = 13; + + // An action link related to this service. + repeated ActionLink action_link = 14; + + enum ServiceType { + SERVICE_TYPE_UNSPECIFIED = 0; + SERVICE_TYPE_DINING_RESERVATION = 1; + SERVICE_TYPE_FOOD_ORDERING = 2; + SERVICE_TYPE_EVENT_TICKET = 3; + SERVICE_TYPE_TRIP_TOUR = 4; + } + + // The predefined type of this service. Currently, only used with action_link. + ServiceType type = 15; + + // Types of tickets that can be booked/purchased for this service, if tickets + // are supported. (optional) + repeated TicketType ticket_type = 16; + + // Photos related to this service. Google will crawl these media to + // ensure that they are displayed to end-users in the most efficient way. + // (optional) + repeated RelatedMedia related_media = 17; + + // Service attribute values that apply to this service (optional). + // Each Service may have zero or more values for each service attribute + // defined in the corresponding Merchant. + repeated ServiceAttributeValueId service_attribute_value_id = 18; +} + +// The price of a service or a fee. +message Price { + // The price in micro-units of the currency. + // Fractions of smallest currency unit will be rounded using nearest even + // rounding. (e.g. For USD 2.5 cents rounded to 2 cents, 3.5 cents rounded to + // 4 cents, 0.5 cents rounded to 0 cents, 2.51 cents rounded to 3 cents). + // (required) + int64 price_micros = 1; + // The currency of the price that is defined in ISO 4217. (required) + string currency_code = 2; + // An optional and opaque string that identifies the pricing option that is + // associated with the extended price. (optional) + string pricing_option_tag = 3; +} +// The scheduling rules for a service. +message SchedulingRules { + // The minimum advance notice in seconds required to book an appointment. + // (optional) + int64 min_advance_booking = 1; + + // The minimum advance notice in seconds required to cancel a booked + // appointment online. (optional) + int64 min_advance_online_canceling = 2; + + // The fee for canceling within the minimum advance notice period. + Price late_cancellation_fee = 3 [deprecated = true]; + + // The fee for no-show without canceling. + Price noshow_fee = 4 [deprecated = true]; +} + +// A tax rate applied when charging the user for a service, and which can be set +// on either a per merchant, or per service basis. +message TaxRate { + // A tax rate in millionths of one percent, effectively giving 6 decimals of + // precision. For example, if the tax rate is 7.253%, this field should be set + // to 7253000. + // + // If this field is left unset or set to 0, the total price charged to a user + // for any service provided by this merchant is the exact price specified by + // Service.price. The service price is assumed to be exempt from or already + // inclusive of applicable taxes. Taxes will not be shown to the user as a + // separate line item. + // + // If this field is set to any nonzero value, the total price charged to a + // user for any service provided by this merchant will include the service + // price plus the tax assessed using the tax rate provided here. Fractions of + // the smallest currency unit (for example, fractions of one cent) will be + // rounded using nearest even rounding. Taxes will be shown to the user as a + // separate line item. (required) + int32 micro_percent = 1; +} + +// Defines a field that is included in a ServiceIntakeForm. +message ServiceIntakeFormField { + // Enum to indicate the type of field. + enum FieldType { + // Fields of unspecified or unknown type will be ignored. + FIELD_TYPE_UNSPECIFIED = 0; + // A one-line input field for text. + SHORT_ANSWER = 1; + // A multi-line input field for text. + PARAGRAPH = 2; + // A set of radio buttons that requires one choice from many options. + MULTIPLE_CHOICE = 3; + // One or more enumerated items with checkboxes. + CHECKBOXES = 4; + // A selection from a dropdown. + DROPDOWN = 5; + // A yes/no button. + BOOLEAN = 6; + } + + // The type of this field. (required) + FieldType type = 1; + + // The text shown to the user for this field. (required) + string label = 2; + + // For MULTIPLE_CHOICE, CHECKBOXES, or DROPDOWN, the values to enumerate. + // (optional) + repeated string value = 3; + + // Indicates whether an answer to this field is required by a user. (optional) + bool is_required = 4; +} + +// Defines an intake form that customizes the service provided by a merchant. +message ServiceIntakeForm { + // Fields that will be displayed to the user. (required) + repeated ServiceIntakeFormField field = 1; + + // If true, this form will be shown to first time customers. + // (one of first_time_customers or returning_customers required) + bool first_time_customers = 2; + + // If true, this form will be shown to repeat customers. + // (one of first_time_customers or returning_customers required) + bool returning_customers = 3; +} + +// A deposit that the user may be charged or have a hold on their credit card +// for. +message Deposit { + // Deposit amount. + Price deposit = 1; + + // Minimum advance cancellation for the deposit. + int64 min_advance_cancellation_sec = 2; + + // Defines how the deposit is determined from the availability. + PriceType deposit_type = 3; +} + +// A fee that a user may be charged if they have made a booking but do not +// show up. +message NoShowFee { + // The amount the user may be charged if they do not show up for their + // reservation. + Price fee = 1; + + // Defines how the fee is determined from the availability. + PriceType fee_type = 3; +} + +// Defines how a total price is determined from an availability. +enum PriceType { + // The price is for a fixed amount. This is the default value if the field is + // not set. + FIXED_RATE_DEFAULT = 0; + // The price specified is per person, and the total price is calculated + // according to the party size specified in Resources as + // price_micros * party_size. A PER_PERSON price must be accompanied by a + // party size in the availability resources. If it is not, a party size of one + // is used. + PER_PERSON = 1; +} + +// Defines whether a credit card is required in order to book an appointment. +enum RequireCreditCard { + // The credit card requirement is not explicitly specified and the + // behaviour is identical to the one specified for CONDITIONAL. + REQUIRE_CREDIT_CARD_UNSPECIFIED = 0; + + // Google will require a credit card for the booking if any of the following + // conditions are met: + // * the availability has a price and the prepayment_type is REQUIRED + // * the no_show_fee is set + // * the deposit field is set. + REQUIRE_CREDIT_CARD_CONDITIONAL = 1; + + // A credit card is always required in order to book this availability + // regardless of other field values. + REQUIRE_CREDIT_CARD_ALWAYS = 2; +} + +// An action URL with associated language, list of countries restricted to, and +// optional platform that indicates which platform this action should be +// performed on. +message ActionLink { + // The entry point URL for this action link. + string url = 1; + + // The BCP-47 language tag identifying the language in which the content + // from this URI is available. + string language = 2; + + // ISO 3166-1 alpha-2 country code. Leave empty for unrestricted visibility. + repeated string restricted_country = 3; + + // The platform that this action should be performed on. If this field is + // unset, ACTION_PLATFORM_WEB_APPLICATION will be used as fallback. + ActionPlatform platform = 4; +} + +// The platform that the action is performed on. Web application is the general +// fallback. It is recommended to have at least one ActionLink with +// ACTION_PLATFORM_WEB_APPLICATION. Links with Android and iOS as platform are +// only used on the respective system. +enum ActionPlatform { + // The platform is unspecified. + ACTION_PLATFORM_UNSPECIFIED = 0; + + // The action platform is web in general. + ACTION_PLATFORM_WEB_APPLICATION = 1; + + // The action platform is web on mobile devices. + ACTION_PLATFORM_MOBILE_WEB = 2; + + // The action platform is Android OS. + ACTION_PLATFORM_ANDROID = 3; + + // The action platform is iOS. + ACTION_PLATFORM_IOS = 4; +} + +// TicketType is used to differentiate among tickets (where a ticket can be a +// spot on a raft trip, an admission to a museum, etc.) with different prices +// and/or availabilities due to different user types or different service +// attributes. +message TicketType { + // The ticket id is used to differentiate among different ticket types of the + // same service, and is only expected to be unique within a service. + string ticket_type_id = 1; + + // This can be user visible, e.g., “adult”, "child", “veteran”, “Row J”, etc. + string short_description = 2; + + // The price of a single ticket of this type, exclusive of any taxes. The tax + // rate of Service is applied to its tickets. + Price price = 3; +} + +// Defines a media source. This field only contains the URL of this media source +// (only photos are supported for now). Google will crawl the media data to +// ensure that they are displayed to end-users in the most efficient way. +message RelatedMedia { + // URL of this media source. + string url = 1; + + // Type of this media source. + MediaType type = 2; + + // Enum to indicate the type of this media source. Only photos are supported. + // Please reach out to Google if supporting animations or videos is + // important to your feature and we will find the best solution for you. + enum MediaType { + // Unused. + TYPE_UNSPECIFIED = 0; + // Indicates the media provided by the url is a photo. + PHOTO = 1; + } + + // Caption of the media, only plain text is supported. (optional) + string caption = 3; +} + +// Identifies a particular value of a service attribute to be applied to a +// Service. +message ServiceAttributeValueId { + // ID of an attribute as defined in Merchant.service_attribute, e.g. + // "service-type". + string attribute_id = 1; + + // ID of the value for this attribute, e.g. "haircut". Must match a value_id + // in the service attribute definition. + string value_id = 2; +}
diff --git a/testclient/main.go b/testclient/bookingBasedTestClient.go similarity index 100% rename from testclient/main.go rename to testclient/bookingBasedTestClient.go
diff --git a/testclient/orderBasedTestClient.go b/testclient/orderBasedTestClient.go new file mode 100644 index 0000000..02125eb --- /dev/null +++ b/testclient/orderBasedTestClient.go
@@ -0,0 +1,215 @@ +/* +Copyright 2017 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "time" + + "github.com/maps-booking-v3/api" + "github.com/maps-booking-v3/utils" + + mpb "github.com/maps-booking-v3/v3" +) + +const logFile = "http_test_client_log_" + +var ( + serverAddr = flag.String("server_addr", "example.com:80", "Your http server's address in the format of host:port") + credentialsFile = flag.String("credentials_file", "", "File containing credentials for your server. Leave blank to bypass authentication. File should have exactly one line of the form 'username:password'.") + testSlots = flag.Int("num_test_slots", 10, "Maximum number of slots to test from availability_feed. Slots will be selected randomly") + allFlows = flag.Bool("all_tests", false, "Whether to test all endpoints syncronously, including ListOrders flow.") + healthFlow = flag.Bool("health_check_test", false, "Whether to test the Health endpoint.") + checkFlow = flag.Bool("check_order_test", false, "Whether to test the CheckOrderFulfillability endpoint.") + orderFlow = flag.Bool("create_order_test", false, "Whether to test the CreateOrder endpoint.") + serviceFeed = flag.String("service_feed", "", "Absolute path to service feed required for all tests except health. Feeds can be in either json or pb3 format") + availabilityFeed = flag.String("availability_feed", "", "Absolute path to availability feed required for all tests except health. Feeds can be in either json or pb3 format") + outputDir = flag.String("output_dir", "", "Absolute path of dir to dump log file.") + caFile = flag.String("ca_file", "", "Absolute path to your server's Certificate Authority root cert. Downloading all roots currently recommended by the Google Internet Authority is a suitable alternative https://pki.google.com/roots.pem. Leave blank to connect using http rather than https.") + fullServerName = flag.String("full_server_name", "", "Fully qualified domain name. Same name used to sign CN. Only necessary if ca_file is specified and the base URL differs from the server address.") +) + +type counters struct { + TotalSlotsProcessed int + HealthCheckSuccess bool + CheckOrderFulfillabilitySuccess int + CheckOrderFulfillabilityErrors int + CreateOrderSuccess int + CreateOrderErrors int + ListOrdersSuccess bool +} + +func createLogFile() (*os.File, error) { + var err error + outPath := *outputDir + if outPath == "" { + outPath, err = os.Getwd() + if err != nil { + return nil, err + } + } + + now := time.Now().UTC() + nowString := fmt.Sprintf("%d-%02d-%02d_%02d-%02d-%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) + outFile := filepath.Join(outPath, fmt.Sprintf("%s%s", logFile, nowString)) + + return os.Create(outFile) +} + +func logStats(stats counters) { + log.Println("\n************* Begin Stats *************\n") + var totalErrors int + if *healthFlow || *allFlows { + if stats.HealthCheckSuccess { + log.Println("HealthCheck Succeeded") + } else { + totalErrors++ + log.Println("HealthCheck Failed") + } + } + if *checkFlow || *allFlows { + totalErrors += stats.CheckOrderFulfillabilityErrors + log.Printf("CheckOrderFulfillability Errors: %d/%d", stats.CheckOrderFulfillabilityErrors, stats.CheckOrderFulfillabilityErrors+stats.CheckOrderFulfillabilitySuccess) + } + if *orderFlow || *allFlows { + totalErrors += stats.CreateOrderErrors + log.Printf("CreateOrder Errors: %d/%d", stats.CreateOrderErrors, stats.CreateOrderErrors+stats.CreateOrderSuccess) + } + if *allFlows { + if stats.ListOrdersSuccess { + log.Println("ListOrders Succeeded") + } else { + totalErrors++ + log.Println("ListOrders Failed") + } + } + if *checkFlow || *orderFlow || *allFlows { + log.Printf("Total Slots Processed: %d", stats.TotalSlotsProcessed) + } + + log.Println("\n\n\n") + if totalErrors == 0 { + log.Println("All Tests Pass!") + } else { + log.Printf("Found %d Errors", totalErrors) + } + + log.Println("\n************* End Stats *************\n") + os.Exit(0) +} + +func main() { + flag.Parse() + var stats counters + + // Set up logging before continuing with flows + f, err := createLogFile() + if err != nil { + log.Fatalf("Failed to create log file %v", err) + } + defer f.Close() + log.SetOutput(f) + + conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName) + if err != nil { + log.Fatalf("Failed to init http connection %v", err) + } + + // HealthCheck Flow + if *healthFlow || *allFlows { + stats.HealthCheckSuccess = true + if err := api.HealthCheck(conn); err != nil { + stats.HealthCheckSuccess = false + log.Println(err.Error()) + } + if !*allFlows && !*checkFlow && !*orderFlow { + logStats(stats) + } + } + + if *availabilityFeed == "" || *serviceFeed == "" { + log.Fatal("please set both availability_feed and service_feed flags") + } + + testInventory, err := utils.MerchantLineItemMapFrom(*serviceFeed, *availabilityFeed, *testSlots) + if err != nil { + log.Fatal(err.Error()) + } + + // CheckOrderFulfillability Flow + if *checkFlow || *allFlows { + utils.LogFlow("CheckOrderFulfillability", "Start") + for _, value := range testInventory { + stats.TotalSlotsProcessed += len(value) + } + + i := 0 + for merchantID, lineItems := range testInventory { + if err = api.CheckOrderFulfillability(merchantID, lineItems, conn); err != nil { + log.Printf("%s. skipping slots %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.TotalSlotsProcessed) + stats.CheckOrderFulfillabilityErrors += len(lineItems) + delete(testInventory, merchantID) + i += len(lineItems) + continue + } + stats.CheckOrderFulfillabilitySuccess += len(lineItems) + i += len(lineItems) + } + utils.LogFlow("CheckOrderFulfillability", "End") + } + // CreateOrder Flow. + var orders []*mpb.Order + if *orderFlow || *allFlows { + utils.LogFlow("CreateOrder", "Start") + if stats.TotalSlotsProcessed == 0 { + for _, value := range testInventory { + stats.TotalSlotsProcessed += len(value) + } + } + + i := 0 + for merchantID, lineItems := range testInventory { + order, err := api.CreateOrder(merchantID, lineItems, conn) + if err != nil { + log.Printf("%s. skipping slot %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.TotalSlotsProcessed) + stats.CreateOrderErrors += len(lineItems) + delete(testInventory, merchantID) + i += len(lineItems) + continue + } + orders = append(orders, order) + stats.CreateOrderSuccess += len(lineItems) + i += len(lineItems) + } + utils.LogFlow("CreateOrder", "End") + } + // ListOrders Flow + if *allFlows { + utils.LogFlow("ListOrders", "Start") + stats.ListOrdersSuccess = true + if err = api.ListOrders(orders, conn); err != nil { + stats.ListOrdersSuccess = false + log.Println(err.Error()) + } + utils.LogFlow("ListOrders", "End") + } + + logStats(stats) +}
diff --git a/utils/utils.go b/utils/utils.go index ac9f064..7e2bf7c 100644 --- a/utils/utils.go +++ b/utils/utils.go
@@ -18,12 +18,14 @@ package utils import ( + "crypto/md5" "errors" "fmt" "io/ioutil" "log" "math/rand" "path" + "sort" "strings" "github.com/golang/protobuf/jsonpb" @@ -47,6 +49,116 @@ log.Println(strings.Join([]string{"\n##########\n", status, f, "Flow", "\n##########"}, " ")) } +func parseServiceFeed(serviceFeed string) ([]*fpb.Service, error) { + var feed fpb.ServiceFeed + content, err := ioutil.ReadFile(serviceFeed) + if err != nil { + return nil, fmt.Errorf("unable to read input file: %v", err) + } + if path.Ext(serviceFeed) == ".json" { + if err := jsonpb.UnmarshalString(string(content), &feed); err != nil { + return nil, fmt.Errorf("unable to parse feed as json: %v", err) + } + } + if path.Ext(serviceFeed) == ".pb3" { + if err := proto.Unmarshal(content, &feed); err != nil { + return nil, fmt.Errorf("unable to parse feed as pb3: %v", err) + } + } + + if services := feed.GetService(); len(services) != 0 { + return services, nil + } + return nil, errors.New("service feed is empty. At least one service must be present in feed") +} + +func merchantService(merchantID, serviceID string) string { + return strings.Join([]string{merchantID, serviceID}, "||") +} + +func buildLineItem(availability *fpb.Availability, tickets []*fpb.TicketType) *mpb.LineItem { + lineItem := &mpb.LineItem{ + ServiceId: availability.GetServiceId(), + StartSec: availability.GetStartSec(), + DurationSec: availability.GetDurationSec(), + Price: &mpb.Price{}, + } + + // If not ticket types return nil as there is nothing to build. + if len(tickets) == 0 { + return nil + } + + for i := 0; i < int(availability.GetSpotsOpen()); i++ { + // This deterministic which is fine given that we just want to get a mix of ticket types. + ticketTypeIndex := rand.Intn(len(tickets)) + // Calculate price of line item. + if lineItem.GetPrice().GetCurrencyCode() == "" && tickets[ticketTypeIndex].GetPrice().GetCurrencyCode() != "" { + lineItem.Price.CurrencyCode = tickets[ticketTypeIndex].GetPrice().GetCurrencyCode() + } + lineItem.Price.PriceMicros += tickets[ticketTypeIndex].GetPrice().GetPriceMicros() + + // Add ticket to line item. + lineItem.Tickets = append(lineItem.Tickets, &mpb.LineItem_OrderedTickets{ + TicketId: tickets[ticketTypeIndex].GetTicketTypeId(), + Count: 1, + }) + } + + return lineItem +} + +// MerchantLineItemMapFrom attempts to build a collection of LineItems from a service and availability feed. +func MerchantLineItemMapFrom(serviceFeed, availabilityFeed string, testSlots int) (map[string][]*mpb.LineItem, error) { + services, err := parseServiceFeed(serviceFeed) + if err != nil { + return nil, err + } + + feedHasTicketType := false + serviceTicketTypeMap := make(map[string][]*fpb.TicketType) + for _, service := range services { + merchantServiceID := merchantService(service.GetMerchantId(), service.GetServiceId()) + for _, ticket := range service.GetTicketType() { + // TicketType can't have an empty price message or ticket_type_id. If it does it's excluded from map. + diff := cmp.Diff(&fpb.Price{}, ticket.GetPrice(), cmp.Comparer(proto.Equal)) + if len(ticket.GetTicketTypeId()) == 0 || diff == "" { + continue + } + + if _, ok := serviceTicketTypeMap[merchantServiceID]; !ok { + serviceTicketTypeMap[merchantServiceID] = []*fpb.TicketType{} + } + feedHasTicketType = true + serviceTicketTypeMap[merchantServiceID] = append(serviceTicketTypeMap[merchantServiceID], ticket) + } + } + + if !feedHasTicketType { + return nil, errors.New("no valid ticket types found in service feed, please update service feed and retry") + } + + availabilities, err := AvailabilityFrom(availabilityFeed, testSlots) + if err != nil { + return nil, err + } + + merchantLineItemMap := make(map[string][]*mpb.LineItem) + for _, availability := range availabilities { + merchantServiceID := merchantService(availability.GetMerchantId(), availability.GetServiceId()) + if tickets, ok := serviceTicketTypeMap[merchantServiceID]; ok { + lineItem := buildLineItem(availability, tickets) + // If lineItem can't be built, don't include in map + if lineItem == nil { + continue + } + merchantLineItemMap[availability.GetMerchantId()] = append(merchantLineItemMap[availability.GetMerchantId()], lineItem) + } + } + + return merchantLineItemMap, nil +} + // AvailabilityFrom parses the file specified in availabilityFeed, returning a random permutation of availability data, maximum entries specified in testSlots func AvailabilityFrom(availabilityFeed string, testSlots int) ([]*fpb.Availability, error) { LogFlow("Parse Input Feed", "Start") @@ -89,7 +201,7 @@ } // ValidateBooking performs granular comparisons between all got and want Bookings. -func ValidateBooking(got *mpb.Booking, want *mpb.Booking) error { +func ValidateBooking(got, want *mpb.Booking) error { if got.GetBookingId() == "" { return errors.New("booking_id is empty") } @@ -114,6 +226,136 @@ return nil } +// ValidateLineItems performs granular comparisons between got and want LineItem arrays. +func ValidateLineItems(got, want []*mpb.LineItem, confirmStatus bool) error { + if len(got) != len(want) { + return fmt.Errorf("number of LineItems differ got %d want %d", len(got), len(want)) + } + + for _, lineItem := range got { + orderTickets := OrderedTickets(lineItem.GetTickets()) + sort.Sort(orderTickets) + lineItem.Tickets = orderTickets + } + for _, lineItem := range want { + orderTickets := OrderedTickets(lineItem.GetTickets()) + sort.Sort(orderTickets) + lineItem.Tickets = orderTickets + + if confirmStatus { + lineItem.Status = mpb.BookingStatus_CONFIRMED + } + } + sort.Sort(LineItems(got)) + sort.Sort(LineItems(want)) + + if diff := cmp.Diff(got, want, cmp.Comparer(proto.Equal)); diff != "" { + return fmt.Errorf("LineItems differ (-got +want)\n%s", diff) + } + + return nil +} + +// ValidateOrder performs granular comparisons between got and want Order messages. +// Params are purposely copied. +func ValidateOrder(got, want mpb.Order) error { + if got.GetOrderId() == "" { + return fmt.Errorf("no order id provided for Order %v", got) + } + want.OrderId = got.GetOrderId() + + if err := ValidateLineItems(got.GetItem(), want.GetItem(), true); err != nil { + return err + } + + // LineItems okay. Remove, free memory, and compare rest of proto. + want.Item = nil + got.Item = nil + if diff := cmp.Diff(got, want, cmp.Comparer(proto.Equal)); diff != "" { + return fmt.Errorf("order protos differ. LineItems excluded, already validated. (-got +want)\n%s", diff) + } + + return nil +} + +// ValidateOrders performs simple comparisons and set up before forwarding orders +// individually to ValidateOrder. +func ValidateOrders(got, want Orders) error { + if len(got) != len(want) { + return fmt.Errorf("number of Orders differ got %d want %d", len(got), len(want)) + } + sort.Sort(got) + sort.Sort(want) + + var errorStrings []string + for i := 0; i < len(got); i++ { + if err := ValidateOrder(*got[i], *want[i]); err != nil { + errorStrings = append(errorStrings, err.Error()) + } + } + + if len(errorStrings) != 0 { + return errors.New(strings.Join(errorStrings, "\n")) + } + return nil +} + +func hashLineItemByTicketIds(l *mpb.LineItem) string { + var uID []string + for _, ticket := range l.GetTickets() { + uID = append(uID, ticket.GetTicketId()) + } + return fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(uID, `|`)))) +} + +// Orders is a convenience type for an Orders array +type Orders []*mpb.Order + +func (o Orders) Len() int { + return len(o) +} + +func (o Orders) Less(i, j int) bool { + return o[i].GetOrderId() < o[j].GetOrderId() +} + +func (o Orders) Swap(i, j int) { + o[i], o[j] = o[j], o[i] +} + +// OrderedTickets is a convenience type for an OrderedTickets array +type OrderedTickets []*mpb.LineItem_OrderedTickets + +func (ot OrderedTickets) Len() int { + return len(ot) +} + +func (ot OrderedTickets) Less(i, j int) bool { + return ot[i].GetTicketId() < ot[j].GetTicketId() +} + +func (ot OrderedTickets) Swap(i, j int) { + ot[i], ot[j] = ot[j], ot[i] +} + +// LineItems is a convenience type for a LineItem array +// This sort interface defined below should be used iff +// OrderedTickets have already been sorted AND +// ticket ids are UIDs +type LineItems []*mpb.LineItem + +func (l LineItems) Len() int { + return len(l) +} + +func (l LineItems) Less(i, j int) bool { + return hashLineItemByTicketIds(l[i]) < hashLineItemByTicketIds(l[j]) +} + +func (l LineItems) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + // BuildSlotFrom creates a bookingservice slot from an feed availability record. func BuildSlotFrom(a *fpb.Availability) (*mpb.Slot, error) { r := a.GetResources()