diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b2a4646..dcccb50 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -41,6 +41,11 @@ jobs: with: go-version: 1.22.2 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Setup JDK uses: actions/setup-java@v4 with: diff --git a/book/lodging/build.gradle.kts b/book/lodging/build.gradle.kts new file mode 100644 index 0000000..c43d13c --- /dev/null +++ b/book/lodging/build.gradle.kts @@ -0,0 +1,46 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + +plugins { + id("com.google.protobuf") +} + +description = "Models comprising the Lodging Book and Manage functionality in the Engine Partner API." + +dependencies { + api(projects.grpcEcosystemProtocGenOpenapiv2) + api(projects.enginePartnerApiCommon) + api(projects.enginePartnerApiContent) + api(projects.enginePartnerApiShopLodging) + api(libs.protobuf.java) + api(libs.protobuf.kotlin) +} + +kotlin { + explicitApi = ExplicitApiMode.Disabled +} + +protobuf { + protoc { + artifact = libs.protobuf.protoc.get().toString() + } + plugins { + create("doc") { + artifact = libs.grpc.protoc.genDoc.get().toString() + } + } + generateProtoTasks { + all().forEach { + it.generateDescriptorSet = true + it.descriptorSetOptions.includeImports = true + + it.plugins { + create("doc") { + option("markdown,${project.name}-${version}.md") + } + } + it.builtins { + create("kotlin") + } + } + } +} diff --git a/book/lodging/src/main/proto/engine/book/lodging/v1/booking.proto b/book/lodging/src/main/proto/engine/book/lodging/v1/booking.proto new file mode 100644 index 0000000..4b7ca64 --- /dev/null +++ b/book/lodging/src/main/proto/engine/book/lodging/v1/booking.proto @@ -0,0 +1,73 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/book/lodging/v1/metadata.proto"; +import "engine/book/lodging/v1/reservation.proto"; +import "engine/book/lodging/v1/status.proto"; +import "engine/common/v1/refund.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging"; +option java_package = "com.engine.book.v1.lodging"; +option java_multiple_files = true; + +// The details of an Engine Lodging [Booking] and the Property [Reservation] it describes. +message BookingDetails { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_BookingDetails_v1" + } + }; + + // A unique identifier for the [Booking]. + string booking_id = 1; + + // Details of the state of the [Booking]. + .engine.book.lodging.v1.BookingStatus status = 2; + + // Details of the Property [Reservation] + .engine.book.lodging.v1.ReservationDetails reservation_details = 3; + + // Optionally, the details of the cancellation action taken for the [Booking]. + .engine.book.lodging.v1.CancellationDetails cancellation_details = 4; + + // Any metadata provided while [Booking]. + // Maximum length: 10 + repeated .engine.book.lodging.v1.BookingMetadata metadata = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + max_items: 10; + }]; +} + +// Details of the cancellation of a [Booking]. +message CancellationDetails { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_CancellationDetails_v1" + } + }; + + // An ISO-8601-compliant date and time at which the [Booking] was canceled. + // See https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + string canceled_at = 1; + + // The details of the Refund granted when canceled. + .engine.common.v1.Refund refund = 2; +} diff --git a/book/lodging/src/main/proto/engine/book/lodging/v1/guests.proto b/book/lodging/src/main/proto/engine/book/lodging/v1/guests.proto new file mode 100644 index 0000000..ff7494b --- /dev/null +++ b/book/lodging/src/main/proto/engine/book/lodging/v1/guests.proto @@ -0,0 +1,79 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/loyalty.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging"; +option java_package = "com.engine.book.v1.lodging"; +option java_multiple_files = true; + +// Details of the guests staying within a single room during the stay. +message RoomGuests { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_RoomGuests_v1" + } + }; + + // The primary guest of a room + .engine.book.lodging.v1.GuestWithLoyalty primary_guest = 1; + + // Any additional names you wish to be able to check-in to the room without the [RoomGuests.primary_guest] present. + // Not all Properties allow additional guests to check-in. + // The primary guest should not be included in the `additional_guests` field. + repeated .engine.book.lodging.v1.Guest additional_guests = 2; +} + +// A traveler who may be awarded Loyalty Rewards for the stay. +message GuestWithLoyalty { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_GuestWithLoyalty_v1" + } + }; + + // The traveler details. + .engine.book.lodging.v1.Guest guest = 1; + + // The identifier to provide to the Property during Booking. + // Invalid identifiers may result in Booking failures with some Properties. + // Not all Offers include loyalty rewards. + .engine.common.v1.LodgingLoyaltyProgramIdentifier loyalty_program_identifier = 2; +} + +// A traveler for the stay. +message Guest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Guest_v1" + } + }; + + // The guest's family name or last name. + string family_name = 1; + + // The guest's given name or first name. + string given_name = 2; + + // True if the guest is an adult. + bool is_adult = 3; +} \ No newline at end of file diff --git a/book/lodging/src/main/proto/engine/book/lodging/v1/metadata.proto b/book/lodging/src/main/proto/engine/book/lodging/v1/metadata.proto new file mode 100644 index 0000000..2be15f9 --- /dev/null +++ b/book/lodging/src/main/proto/engine/book/lodging/v1/metadata.proto @@ -0,0 +1,54 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/loyalty.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging"; +option java_package = "com.engine.book.v1.lodging"; +option java_multiple_files = true; + +// Custom Metadata attached to a [Booking]. +// Metadata values allow a Partner API customer to carry a small amount of context on a [Booking] for their own use. +message BookingMetadata { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_BookingMetadata_v1" + } + }; + + // A textual key to represent the purpose of the metadata, for example, "Contract ID". + // Keys must be unique within a [Booking]. + // Maximum length: 40 characters + string key = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + max_length: 40; + }]; + + // A textual value. + // Maximum length: 100 characters + string value = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + max_length: 100; + }]; + + // If true, this Metadata entry will be included on invoices and reports. + // If false, this entry will only be available via direct API access. + bool external = 3; +} \ No newline at end of file diff --git a/book/lodging/src/main/proto/engine/book/lodging/v1/quote.proto b/book/lodging/src/main/proto/engine/book/lodging/v1/quote.proto new file mode 100644 index 0000000..964b392 --- /dev/null +++ b/book/lodging/src/main/proto/engine/book/lodging/v1/quote.proto @@ -0,0 +1,60 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/actions.proto"; +import "engine/shop/lodging/v1/offer.proto"; +import "engine/shop/lodging/v1/room.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging"; +option java_package = "com.engine.book.v1.lodging"; +option java_multiple_files = true; + +// The full details of the item to be Booked. +message Quote { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Quote_v1" + } + }; + + // An ISO-8601-compliant date on which the stay begins. + // See https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates + string check_in_date = 1; + + // An ISO-8601-compliant date on which the stay ends. + // See https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates + string check_out_date = 2; + + // The details of the [Offer] to be Booked. + .engine.shop.lodging.v1.OfferSummary offer_summary = 3; + + // The number of rooms to be Booked. + int32 room_count = 4; + + // The details of the [Room] to be booked. + .engine.shop.lodging.v1.RoomDescription room_description = 5; + + // Optionally, the actions (such as cancellation) that are available for the [Booking] if they are known. + // Not every action will be available through the API for every reservation. + // Some may be available but the availability is not known at the point of the [LodgingBookingService.ConfirmOffer] or the [LodgingBookingService.Book] request. + .engine.common.v1.AvailableActions available_actions = 6; +} \ No newline at end of file diff --git a/book/lodging/src/main/proto/engine/book/lodging/v1/reservation.proto b/book/lodging/src/main/proto/engine/book/lodging/v1/reservation.proto new file mode 100644 index 0000000..768169f --- /dev/null +++ b/book/lodging/src/main/proto/engine/book/lodging/v1/reservation.proto @@ -0,0 +1,52 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/book/lodging/v1/guests.proto"; +import "engine/book/lodging/v1/metadata.proto"; +import "engine/book/lodging/v1/quote.proto"; +import "engine/common/v1/refund.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging"; +option java_package = "com.engine.book.v1.lodging"; +option java_multiple_files = true; + +// Details of the Reservation made with the [Property]. +message ReservationDetails { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_ReservationDetails_v1" + } + }; + + // The confirmation numbers provided by the [Property] after a successful [Reservation]. + // Confirmation numbers may not be available immediately after booking. + // The quantity of confirmation numbers may vary by the [Property] booked. + repeated string confirmation_numbers = 1; + + // An ISO-8601-compliant date and time at which the [Reservation] was booked. + // See https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + string created_at = 2; + + // The details of the [Quote] that was purchased. + .engine.book.lodging.v1.Quote quote = 3; +} + diff --git a/book/lodging/src/main/proto/engine/book/lodging/v1/status.proto b/book/lodging/src/main/proto/engine/book/lodging/v1/status.proto new file mode 100644 index 0000000..6bb6af6 --- /dev/null +++ b/book/lodging/src/main/proto/engine/book/lodging/v1/status.proto @@ -0,0 +1,56 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/loyalty.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging"; +option java_package = "com.engine.book.v1.lodging"; +option java_multiple_files = true; + +// Details describing the observed state of the [Booking]. +message BookingStatus { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_BookingStatus_v1" + } + }; + + .engine.book.lodging.v1.LodgingBookingStatusCode code = 1; +} + +// A value representing the state of a [Booking]. +enum LodgingBookingStatusCode { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { + title: "Book_Lodging_LodgingBookingStatusCode_v1" + }; + + LODGING_BOOKING_STATUS_UNSPECIFIED = 0; + + // The Booking has been submitted, but has not yet been confirmed. + LODGING_BOOKING_STATUS_PENDING = 1; + + // The Booking has been confirmed. + LODGING_BOOKING_STATUS_CONFIRMED = 2; + + // The Booking has been canceled and is no longer active. + LODGING_BOOKING_STATUS_CANCELED = 3; +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 39d0ccb..f0b651d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,19 +1,30 @@ -import org.gradle.api.internal.catalog.DelegatingProjectDependency +import com.google.gson.GsonBuilder +import org.gradle.api.internal.provider.DefaultProvider import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.util.capitalizeDecapitalize.toUpperCaseAsciiOnly import org.jlleitschuh.gradle.ktlint.KtlintExtension import org.jlleitschuh.gradle.ktlint.reporter.ReporterType import org.jreleaser.gradle.plugin.JReleaserExtension import org.jreleaser.model.Active -import java.util.Calendar +import java.nio.file.Files +import java.util.* +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.isSymbolicLink plugins { id("org.jreleaser") - + id("com.google.protobuf").apply(false) kotlin("jvm").apply(false) id("org.jlleitschuh.gradle.ktlint").apply(false) } +buildscript { + dependencies { + classpath(buildscript.gson) + } +} + fun calculateVersion(): String { return System .getenv("ENGINE_BUILD_VERSION") @@ -27,6 +38,7 @@ fun calculateVersion(): String { group = "com.engine" version = calculateVersion() +description = "engine.com Partner API Definitions" val distDir = project.layout.buildDirectory.dir("dist") val stagingDir = project.layout.buildDirectory.dir("staging") @@ -212,14 +224,16 @@ tasks.register("writeVersion") { } } -val protoProjects = listOf( - projects.grpcEcosystemProtocGenOpenapiv2, - projects.enginePartnerApiCommon, - projects.enginePartnerApiContent, - projects.enginePartnerApiService -) - afterEvaluate { + // Find the projects that have proto files + val protoProjects = project + .subprojects + .filter { p -> + !fileTree(p.layout.projectDirectory.dir("src/main/proto")) { + include("**/*.proto") + }.isEmpty + } + val protosets = copySpec { // copy the descriptor set from the service project from(projects.enginePartnerApiService.dependencyProject.layout.buildDirectory.dir("generated/source/proto/main")) { @@ -227,17 +241,196 @@ afterEvaluate { } } + val swaggerFiles = fileTree(projects.enginePartnerApiService.dependencyProject.layout.buildDirectory.dir("generated/source/proto/main/openapiv2")) { + include("**/service.swagger.json") + } + + val swaggerDir = project.layout.buildDirectory.dir("swagger") + val baseSwaggerJson = swaggerDir.map { it.file("base.swagger.json") } + val openApiMergeJson = swaggerDir.map { it.file("openapi-merge.json") } + val enginePartnerApiSwaggerJson = swaggerDir.map { it.file("engine-partner-api.swagger.json") } + + val gson = DefaultProvider { + GsonBuilder().setPrettyPrinting().create() + } + + val generateBaseSwagger = tasks.register("generateBaseSwagger") { + group = "swagger" + description = "Generates the base swagger.json file containing the API metadata." + outputs.file(baseSwaggerJson) + doLast { + baseSwaggerJson + .get() + .asFile + .writeText( + gson + .get() + .toJson( + mapOf( + "swagger" to "2.0", + "info" to mapOf( + "title" to "Engine Partner API", + "description" to project.description, + "version" to project.version.toString(), + "contact" to mapOf( + "name" to "Engine Partner API Support", + "url" to "https://engine.com", + "email" to "partner-api-support@engine.com" + ), + "license" to mapOf( + "name" to "Apache License Version 2.0", + "url" to "https://github.com/engine-public/engine-partner-api/blob/main/LICENSE" + ) + ), + "externalDocs" to "https://github.com/engine-public/engine-partner-api/docs/README.md", + "host" to "partner-api.engine.com", + "schemes" to listOf("https"), + "consumes" to listOf("application/json"), + "produces" to listOf("application/json") + ) + ) + ) + } + } + + val generateOpenApiMergeJson = tasks.register("generateOpenApiMergeJson") { + group = "swagger" + description = "Generates the configuration file used to merge OpenAPI definitions." + + // The swagger files are generated by protoc during the subprojects generateProto tasks + protoProjects.forEach { + dependsOn(it.tasks.findByName("generateProto")) + } + + outputs.file(openApiMergeJson) + + doLast { + openApiMergeJson + .get() + .asFile + .writeText( + gson.get().toJson( + mapOf( + "inputs" to listOf( + baseSwaggerJson.get().asFile, + *swaggerFiles.toList().toTypedArray() + ).map { + mapOf("inputFile" to it.relativeTo(openApiMergeJson.get().asFile.parentFile).path) + }, + "output" to enginePartnerApiSwaggerJson.get().asFile.relativeTo(enginePartnerApiSwaggerJson.get().asFile.parentFile).path + ) + ) + ) + } + } + + fun resolveCommand(command: String): String { + val originalPath = ProcessBuilder().command("which", command).start().let { process -> + process.inputReader(Charsets.UTF_8).use { reader -> + Path(reader.readText().trim()) + } + } + if (!originalPath.isSymbolicLink()) { + return command + } + var resolvedPath = originalPath + while (resolvedPath.isSymbolicLink()) { + Files.readSymbolicLink(resolvedPath).let { + resolvedPath = if (it.isAbsolute) it else resolvedPath.parent.resolve(it) + } + } + return resolvedPath.absolutePathString().also { + logger.info("Resolved command $command to $resolvedPath") + } + } + + + val npxCommand = resolveCommand("npx") + + val mergeSwagger = tasks.register("mergeSwagger") { + group = "swagger" + description = "Merges all of the generated swagger service files into a single Engine Partner API swagger definition." + + protoProjects.forEach { + dependsOn(it.tasks.findByName("generateProto")) + } + dependsOn(generateBaseSwagger) + dependsOn(generateOpenApiMergeJson) + + inputs.file(baseSwaggerJson) + inputs.file(openApiMergeJson) + inputs.files(swaggerFiles) + outputs.file(enginePartnerApiSwaggerJson) + + doLast { + val services: List> = swaggerFiles.map { gson.get().fromJson(it.readText(), Map::class.java) as Map } + + val allTags: Set = services.flatMap { service -> + (service["paths"] as Map>>).flatMap { (path, methods) -> methods.flatMap { (method, keys) -> (keys["tags"] as List)}} + }.toSet() + + val allPaths: MutableMap> = mutableMapOf() + services + .map { service -> (service["paths"] as Map>)} + .forEach { servicePaths: Map> -> + servicePaths.forEach { (path, methods) -> + methods.forEach { (method, details) -> + if (allPaths.containsKey(path) && allPaths[path]!!.containsKey(method)) { + throw Exception("Conflicting definition of $path and $method:\n${allPaths[path]!![method]}\n-----\n${details}") + } else { + allPaths.computeIfAbsent(path) { mutableMapOf() }.set(method, details!!) + } + } + } + } + + + val allDefinitions = mutableMapOf() + services + .map { service -> service["definitions"] as Map } + .forEach { serviceDefs: Map -> + serviceDefs.forEach { (definitionName, value) -> + if (allDefinitions.containsKey(definitionName)) { + if (allDefinitions[definitionName] != value) { + throw Exception("Conflicting definition of $definitionName: ${allDefinitions[definitionName]}\n!=\n${value}") + } + } else { + allDefinitions[definitionName] = value!! + } + } + } + + val base = gson.get().fromJson(baseSwaggerJson.get().asFile.readText(), MutableMap::class.java) as MutableMap + base["tags"] = allTags.map { + mapOf("name" to it) + } + base["paths"] = allPaths + base["definitions"] = allDefinitions + + enginePartnerApiSwaggerJson.get().asFile.writeText( + gson.get().toJson(base) + ) + } + } + + val stageJsonContent = tasks.register("stageJsonContent") { + dependsOn(mergeSwagger) + from(enginePartnerApiSwaggerJson) + into(documentationStagingDir.map { it.dir("JSON") }) + includeEmptyDirs = false + } + val stageProtoContent = tasks.register("stageProtoContent") { protoProjects.forEach { p -> - dependsOn(p.dependencyProject.tasks["generateProto"]) + dependsOn(p.tasks["generateProto"]) } // copy all protos and docs from all subprojects, maintaining namespace - protoProjects.forEach { p: DelegatingProjectDependency -> - from(p.dependencyProject.layout.projectDirectory.dir("src/main/proto")) { + protoProjects.forEach { p -> + from(p.layout.projectDirectory.dir("src/main/proto")) { include("**/*.proto") } - from(p.dependencyProject.layout.buildDirectory.dir("generated/source/proto/main/doc")) { + from(p.layout.buildDirectory.dir("generated/source/proto/main/doc")) { include("**/*.html", "**/*.md") } } @@ -258,24 +451,6 @@ afterEvaluate { includeEmptyDirs = false } - val swaggers = copySpec { - from(projects.enginePartnerApiService.dependencyProject.layout.buildDirectory.dir("generated/source/proto/main/openapiv2")) { - include("**/service.swagger.json") - eachFile { - path = "${path.split("/")[1]}.swagger.json" - } - } - } - - val stageJsonContent = tasks.register("stageJsonContent") { - protoProjects.forEach { - dependsOn(it.dependencyProject.tasks.findByName("generateProto")) - } - with(swaggers) - into(documentationStagingDir.map { it.dir("JSON") }) - includeEmptyDirs = false - } - val stageDocContent = tasks.register("stageDocContent") { from("docs/") from("LICENSE") @@ -297,9 +472,10 @@ afterEvaluate { tasks.register("buildApiDefinitionDistribution") { group = "publishing" protoProjects.forEach { - dependsOn(it.dependencyProject.tasks.findByName("generateProto")) + dependsOn(it.tasks.findByName("generateProto")) } - with(swaggers) + dependsOn(mergeSwagger) + from(enginePartnerApiSwaggerJson) with(protosets) into(distDir) includeEmptyDirs = false @@ -309,4 +485,3 @@ afterEvaluate { dependsOn(stageMavenCentral) } } - diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 21dca17..a3d076e 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -32,7 +32,7 @@ protobuf { it.plugins { create("doc") { - option("html,${project.name}-${version}.html") + option("markdown,${project.name}-${version}.md") } } it.builtins { diff --git a/common/src/main/proto/engine/common/v1/actions.proto b/common/src/main/proto/engine/common/v1/actions.proto new file mode 100644 index 0000000..94d8c22 --- /dev/null +++ b/common/src/main/proto/engine/common/v1/actions.proto @@ -0,0 +1,62 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import 'protoc-gen-openapiv2/options/annotations.proto'; + +// Describes the availability of actions on a [Booking]. +message AvailableActions { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_AvailableActions_v1" + } + }; + + // Describes the availability of the cancel action. + .engine.common.v1.ActionAvailability cancel = 1; + + // Describes the availability of the modify action. + .engine.common.v1.ActionAvailability modify = 2; +} + +// Describes the means by which an action may be taken on a [Booking]. +enum ActionAvailability { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { + title: "Common_ActionAvailability_v1" + }; + + // It is unknown if the action is available. It might be available directly through the vendor. + ACTION_AVAILABILITY_UNKNOWN = 0; + + // The action is not available through the API. It is unknown whether it can be performed in another context, such as through the vendor or support. + ACTION_AVAILABILITY_UNAVAILABLE = 1; + + // The action is available through the API. + ACTION_AVAILABILITY_AVAILABLE = 2; + + // The action is available, but only through the service provider. + ACTION_AVAILABILITY_AVAILABLE_VIA_VENDOR = 3; + + // The action is available, but only through customer support. + ACTION_AVAILABILITY_AVAILABLE_VIA_SUPPORT = 4; +} \ No newline at end of file diff --git a/common/src/main/proto/engine/common/v1/amenities.proto b/common/src/main/proto/engine/common/v1/amenities.proto new file mode 100644 index 0000000..18bf4a4 --- /dev/null +++ b/common/src/main/proto/engine/common/v1/amenities.proto @@ -0,0 +1,46 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import 'protoc-gen-openapiv2/options/annotations.proto'; + + +// Describes the condition of an amenity's inclusion on a [Property], [Room], [Offer], or [Booking]. +enum AmenityAvailability { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { + title: "Common_AmenityAvailability_v1" + }; + + // The availability of an amenity cannot be confirmed. + AMENITY_AVAILABILITY_UNKNOWN = 0; + + // The amenity is confirmed to not be available. + AMENITY_AVAILABILITY_UNAVAILABLE = 1; + + // The amenity is confirmed to be included. + AMENITY_AVAILABILITY_INCLUDED = 2; + + // The amenity is not included, but may be purchased directly from the hotel, airline, car rental vendor, etc. + // For example, a seat upgrade or breakfast vouchers. + AMENITY_AVAILABILITY_VIA_VENDOR = 3; +} \ No newline at end of file diff --git a/common/src/main/proto/engine/common/v1/conditions.proto b/common/src/main/proto/engine/common/v1/conditions.proto new file mode 100644 index 0000000..fda5527 --- /dev/null +++ b/common/src/main/proto/engine/common/v1/conditions.proto @@ -0,0 +1,90 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import 'protoc-gen-openapiv2/options/annotations.proto'; + +import 'engine/common/v1/currency.proto'; + +// Conditions that describe the availability, penalty, and timing constraints on modification to a booked item. +message Conditions { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_Conditions_v1" + } + }; + + // Conditions that apply to modification of a [Booking]. + // If no modify_conditions exist, the [Booking] may not be modified. + repeated .engine.common.v1.Condition modify_conditions = 1; + + // Conditions that apply to cancellation of a [Booking]. + // If no cancel_conditions exist, the [Booking] may not be canceled. + repeated .engine.common.v1.Condition cancel_conditions = 2; +} + +// An individual condition to be applied to an interaction with a booked item. +// For example, to cancel, or extend a Lodging [Booking]. +message Condition { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_Condition_v1" + } + }; + + // If present, this condition applies before and up to this ISO-8601-compliant timestamp. + // For example, Changing this flight is free before May 30th. + // This field may interact with [Condition.after_timestamp]. + // See https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + optional string before_timestamp = 1; + + // If present, this condition applies on or after this ISO-8601-compliant timestamp. + // For example, Canceling this flight incurs a $75 fee after June 1st. + // This field may interact with [Condition.before_timestamp] and [Condition.before_event]. + // See https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + optional string after_timestamp = 2; + + // If present, this condition applies up until the described event occurs. + // For example, Cancelling this flight is free until departure. + optional .engine.common.v1.ConditionEventType before_event = 3; + + // Optionally, any charge incurred as a result of the intended action within the constraints of + // [Condition.before_timestamp], [Condition.after_timestamp], and [Condition.before_event]. + // penalty may be absent if the penalty is unknown. A missing penalty does not imply that there is no penalty. + // If the penalty is known to be zero, penalty will be present with a value of 0. + .engine.common.v1.CurrencyValue penalty = 5; + + // If false, the intended operation is not allowed. + optional bool is_allowed = 6; +} + +// Events that affect the availability of an action when a specific timestamp is not available. +enum ConditionEventType { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { + title: "Common_ConditionEventType_v1" + }; + + CONDITION_EVENT_TYPE_UNKNOWN = 0; + CONDITION_EVENT_TYPE_DEPARTURE = 1; + CONDITION_EVENT_TYPE_CHECKIN = 2; +} diff --git a/common/src/main/proto/engine/common/v1/currency.proto b/common/src/main/proto/engine/common/v1/currency.proto new file mode 100644 index 0000000..89ae777 --- /dev/null +++ b/common/src/main/proto/engine/common/v1/currency.proto @@ -0,0 +1,43 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import 'protoc-gen-openapiv2/options/annotations.proto'; + +// A safe encoding of a monetary value and its paired currency code. +message CurrencyValue { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_CurrencyValue_v1" + } + }; + + // The 3-character ISO-4217 alphabetic currency code. + // For example: USD + // See https://www.iso.org/iso-4217-currency-codes.html + string currency_code = 1; + + // The non-localized decimal representation of the amount of currency represented by this object. + // For example, "123456.789". + string value = 2; +} \ No newline at end of file diff --git a/common/src/main/proto/engine/common/v1/geo.proto b/common/src/main/proto/engine/common/v1/geo.proto index 8045eb4..af9def3 100644 --- a/common/src/main/proto/engine/common/v1/geo.proto +++ b/common/src/main/proto/engine/common/v1/geo.proto @@ -28,7 +28,7 @@ import 'protoc-gen-openapiv2/options/annotations.proto'; message GeoPoint { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { json_schema: { - title: "GeoPoint_v1" + title: "Common_GeoPoint_v1" } }; @@ -43,7 +43,7 @@ message GeoPoint { message GeoRadius { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { json_schema: { - title: "GeoRadius_v1" + title: "Common_GeoRadius_v1" } }; @@ -58,7 +58,7 @@ message GeoRadius { message Distance { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { json_schema: { - title: "Distance_v1" + title: "Common_Distance_v1" } }; @@ -72,7 +72,7 @@ message Distance { // The unit type for a distance measurement. enum DistanceUnit { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { - title: "DistanceUnit_v1" + title: "Common_DistanceUnit_v1" }; // A mile in the imperial system of units. diff --git a/common/src/main/proto/engine/common/v1/loyalty.proto b/common/src/main/proto/engine/common/v1/loyalty.proto new file mode 100644 index 0000000..2027dbb --- /dev/null +++ b/common/src/main/proto/engine/common/v1/loyalty.proto @@ -0,0 +1,39 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import 'protoc-gen-openapiv2/options/annotations.proto'; + +// An identifier signifying a traveler's membership in a Loyalty Program. +// Loyalty Program Identifiers are provided by the administrator of that program. +// Not all [Offer]s will grant loyalty rewards. +message LodgingLoyaltyProgramIdentifier { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_LodgingLoyaltyProgramIdentifier_v1" + } + }; + + // The identifier provided to the travel by the program administrator. + string member_id = 1; +} \ No newline at end of file diff --git a/common/src/main/proto/engine/common/v1/postal_address.proto b/common/src/main/proto/engine/common/v1/postal_address.proto index f3a661a..98c1b76 100644 --- a/common/src/main/proto/engine/common/v1/postal_address.proto +++ b/common/src/main/proto/engine/common/v1/postal_address.proto @@ -29,7 +29,7 @@ import "protoc-gen-openapiv2/options/annotations.proto"; message PostalAddress { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { json_schema: { - title: "PostalAddress_v1" + title: "Common_PostalAddress_v1" } }; @@ -39,7 +39,7 @@ message PostalAddress { repeated string recipients = 1; // Optionally, any organization or company as a recipient. - // For example, "Hotel Engine". + // For example, "Engine". optional string organization = 2; // Optionally, any address lines, in order as they'd appear on an envelope. diff --git a/common/src/main/proto/engine/common/v1/price.proto b/common/src/main/proto/engine/common/v1/price.proto new file mode 100644 index 0000000..2a20261 --- /dev/null +++ b/common/src/main/proto/engine/common/v1/price.proto @@ -0,0 +1,160 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import "protoc-gen-openapiv2/options/annotations.proto"; +import "engine/common/v1/currency.proto"; + + +// The details of the cost of an item or service. +message Price { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_Price_v1" + } + }; + + // The sum total for the booking excluding taxes and fees. + // The subtotal includes all [Price.line_items]. + .engine.common.v1.CurrencyValue sub_total = 1; + + // The full liability to purchase the item. + // This is not necessarily the amount due at time of booking due to discounts, incentives, or postpaid items. + .engine.common.v1.CurrencyValue total = 2; + + // Any taxes to be collected at time of payment. + repeated .engine.common.v1.Surcharge taxes = 3; + + // Any fees applied to the item or service. + repeated .engine.common.v1.Surcharge fees = 4; + + // The cost of the main item or service in the booking, less upgrades or additional services, fees, and taxes. + // The base is equivalent to: [Price.sub_total] - sum([Price.line_items]) + .engine.common.v1.CurrencyValue base = 5; + + // Additional upgrades to an item or service added during the shopping experience. + // Line items are represented in the [Price.sub_total]. + repeated .engine.common.v1.PriceLineItem line_items = 6; + + // The sum of all individual [Price.taxes] and [Price.fees]. + .engine.common.v1.CurrencyValue taxes_and_fees_total = 7; + + // A representative market price for the item or service without any discounts applied. + .engine.common.v1.CurrencyValue strike = 8; + + // The total amount due at time of booking. + .engine.common.v1.CurrencyValue total_due_now = 10; + + // The total amount due at the time of the travel event. + .engine.common.v1.CurrencyValue total_due_later = 12; +} + +// An additional charge, fee, or tax. +message Surcharge { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_Surcharge_v1" + } + }; + + // A textual description of the purpose of the Surcharge. + string description = 1; + + // The amount levied. + .engine.common.v1.CurrencyValue amount = 2; + + // The type of surcharge + .engine.common.v1.SurchargeType type = 3; +} + +// A programmatic classification of types of surcharges that could be levied. +enum SurchargeType { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { + title: "Common_SurchargeType_v1" + }; + + // A fee due at time of booking. + SURCHARGE_TYPE_PREPAID_FEE = 0; + + // A fee due at time of travel. + SURCHARGE_TYPE_POSTPAID_FEE = 1; + + // Taxes due at time of booking. + SURCHARGE_TYPE_TAX = 2; +} + +// A individual service or upgrade added to an item during the shopping experience. +message PriceLineItem { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_PriceLineItem_v1" + } + }; + + // A textual description of the individual charge. + string description = 1; + + // The price of this line item. + .engine.common.v1.CurrencyValue price = 2; + + // Optionally, an identifier to group line items for visual display. + // For example, "Extras" or "Loyalty Benefits". + optional string group = 3; +} + +// The total price, along with a breakdown by a given unit. +// For example, the total stay cost and the per-night cost. +message PriceWithPerUnit { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_PriceWithPerUnit_v1" + } + }; + + // The total price for the item. + .engine.common.v1.Price price = 1; + + // The total price broken into per-unit costs + repeated .engine.common.v1.PerUnitPrice per_unit_price = 2; +} + +// A price representing the cost of a single unit in a multi-unit breakdown. +// For example, price per night. +message PerUnitPrice { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_PerUnitPrice_v1" + } + }; + + // A key to distinguish which unit is represented by this price. + // For example, which date, or which passenger. + string key = 1; + + // The type of unit. + // For example, "night" or "person". + string unit = 2; + + // The cost for this unit. + .engine.common.v1.Price price = 3; +} \ No newline at end of file diff --git a/common/src/main/proto/engine/common/v1/refund.proto b/common/src/main/proto/engine/common/v1/refund.proto new file mode 100644 index 0000000..0f2d69e --- /dev/null +++ b/common/src/main/proto/engine/common/v1/refund.proto @@ -0,0 +1,38 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import "protoc-gen-openapiv2/options/annotations.proto"; +import "engine/common/v1/currency.proto"; + +// Details that describe any refund due or granted related to a modification or cancellation of a [Booking]. +message Refund { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_Refund_v1" + } + }; + + // The full amount of the refund. + .engine.common.v1.CurrencyValue amount = 1; +} diff --git a/common/src/main/proto/engine/common/v1/sales_channel.proto b/common/src/main/proto/engine/common/v1/sales_channel.proto new file mode 100644 index 0000000..232a3c7 --- /dev/null +++ b/common/src/main/proto/engine/common/v1/sales_channel.proto @@ -0,0 +1,44 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +// Describes the source of client +enum SalesChannel { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { + title: "Common_SalesChannel_v1" + }; + + // The sales channel cannot be determined. + SALES_CHANNEL_UNKNOWN = 0; + + // The customer is shopping via a mobile application. + SALES_CHANNEL_MOBILE_NATIVE = 1; + + // The customer is shopping via a mobile web browser. + SALES_CHANNEL_MOBILE_WEB = 2; + + // The customer is shopping via a web browser on a desktop or laptop computer. + SALES_CHANNEL_WEB = 3; +} \ No newline at end of file diff --git a/common/src/main/proto/engine/common/v1/traveler.proto b/common/src/main/proto/engine/common/v1/traveler.proto new file mode 100644 index 0000000..50a336e --- /dev/null +++ b/common/src/main/proto/engine/common/v1/traveler.proto @@ -0,0 +1,69 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.common.v1; + +option go_package = "engine.com/engine-partner-api/common/v1"; +option java_package = "com.engine.common.v1"; +option java_multiple_files = true; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +// A traveler whose personal details have not yet been collected. +message AnonymousTraveler { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_AnonymousTraveler_v1" + } + }; + + oneof traveler_type { + .engine.common.v1.AnonymousAdult adult = 1; + .engine.common.v1.AnonymousChild child = 2; + } +} + +// An adult traveler whose personal details have not yet been collected. +message AnonymousAdult { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_AnonymousAdult_v1" + } + }; +} + +// A child traveler whose personal details have not yet been collected. +// A child's age is necessary to retrieve the correct prices. +// Failure to provide an accurate age may result in loss of bookings or refusal to provide service to the traveler. +message AnonymousChild { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Common_AnonymousChild_v1" + } + }; + + oneof age_definition_type { + // The child's age on the date of travel. + // If the child's birthday is during travel, use the age they will be at the end of travel. + int32 age = 1; + + // An ISO-8601-compliant date representing the child's birthdate. + // See https://en.wikipedia.org/wiki/ISO_8601#Dates + string birthdate = 2; + } +} diff --git a/content/build.gradle.kts b/content/build.gradle.kts index f7b7153..03d2827 100644 --- a/content/build.gradle.kts +++ b/content/build.gradle.kts @@ -44,7 +44,7 @@ protobuf { create("grpc") create("grpckt") create("doc") { - option("html,${project.name}-${version}.html") + option("markdown,${project.name}-${version}.md") } } it.builtins { diff --git a/content/src/main/proto/engine/content/v1/lodging/property.proto b/content/src/main/proto/engine/content/v1/lodging/property.proto index 3a9fece..e0dd42d 100644 --- a/content/src/main/proto/engine/content/v1/lodging/property.proto +++ b/content/src/main/proto/engine/content/v1/lodging/property.proto @@ -32,7 +32,7 @@ import "protoc-gen-openapiv2/options/annotations.proto"; message Property { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { json_schema: { - title: "Property_v1" + title: "Content_Property_v1" } }; diff --git a/docs/JSON/compose.yaml b/docs/JSON/compose.yaml index ac4ff00..9eb1f09 100644 --- a/docs/JSON/compose.yaml +++ b/docs/JSON/compose.yaml @@ -1,7 +1,7 @@ services: swagger: environment: - SWAGGER_JSON: /var/specs/content.swagger.json + SWAGGER_JSON: /var/specs/engine-public-api.swagger.json image: swaggerapi/swagger-ui ports: - target: 8080 diff --git a/docs/link_footer.md b/docs/link_footer.md new file mode 100644 index 0000000..39a9803 --- /dev/null +++ b/docs/link_footer.md @@ -0,0 +1,17 @@ +[Booking]: # +[Condition.after_timestamp]: # +[Condition.before_event]: # +[Condition.before_timestamp]: # +[Engine]: https://www.engine.com +[LodgingBookingService.ConfirmOffer]: # +[LodgingBookingService.Book]: # +[Offer]: # +[Price.fees]: # +[Price.line_items]: # +[Price.sub_total]: # +[Price.taxes]: # +[Property]: # +[RedactedData]: # +[Reservation]: # +[Room]: # +[RoomGuests.primary_guest]: # diff --git a/docs/members_deep_linking.md b/docs/members_deep_linking.md index 8337277..784e974 100644 --- a/docs/members_deep_linking.md +++ b/docs/members_deep_linking.md @@ -3,6 +3,8 @@ --- ## Book a room for a given property +This link will take you directly to the room and rate selection page within our Members web experience. + ### URL `https://members.engine.com/properties/:propertyid?checkIn=:checkin&checkOut=:checkout&roomCount=:roomcount&guestCount=:guestcount` @@ -21,5 +23,26 @@ | roomcount | no | The number of rooms you wish to book. | 1 | | guestcount | no | The number of guests for the stay. | 2 | +--- +## Signup or Signin with your Custom Landing Experience + +This link will allow you to direct your customers to your custom landing page. +Once the customer has signed in or created their new account, they will be redirected to the provided URL or to their dashboard. + +### URL +`https://members.engine.com/join/:slug?redirect_url=:members_deep_link_url` + +### Path Parameters + +| name | required | description | default | +|------|----------|-------------------------------------------------------|---------| +| slug | yes | The slug provided for your custom landing experience. | none | + +### Query Parameters + +| name | required | description | default | +|-----------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| +| members_deep_link_url | no | A URL-encoded deep link into the Engine Members Web Experience, for example, [Book a room for a given property](#book-a-room-for-a-given-property) | If no `members_deep_link_url` is provided, the customer will be sent to the Members Dashboard. | + --- [ISO-8601 Calendar Date]: https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates diff --git a/gradle.properties b/gradle.properties index d8e6ce1..c60a18b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,5 +5,4 @@ kotlinVersion=1.9.22 version.plugin.ktlint=12.1.2 version.plugin.protobuf=0.9.4 -version.plugin.version=1.0.0 -version.plugin.jreleaser=1.16.0 +version.plugin.jreleaser=1.18.0 diff --git a/grpc-ecosystem-protoc-gen-openapiv2/build.gradle.kts b/grpc-ecosystem-protoc-gen-openapiv2/build.gradle.kts index e37a784..3a2d136 100644 --- a/grpc-ecosystem-protoc-gen-openapiv2/build.gradle.kts +++ b/grpc-ecosystem-protoc-gen-openapiv2/build.gradle.kts @@ -113,7 +113,7 @@ protobuf { } task.plugins { create("doc") { - option("html,${project.name}-$version.html") + option("markdown,${project.name}-$version.md") } } } diff --git a/service/build.gradle.kts b/service/build.gradle.kts index de2dadd..7777b21 100644 --- a/service/build.gradle.kts +++ b/service/build.gradle.kts @@ -12,6 +12,8 @@ description = "The Engine Partner API gRPC and OpenAPI service definitions." dependencies { api(projects.enginePartnerApiContent) + api(projects.enginePartnerApiShopLodging) + api(projects.enginePartnerApiBookLodging) api(libs.grpc.core) api(libs.grpc.kotlin) @@ -86,7 +88,7 @@ protobuf { create("grpc") create("grpckt") create("doc") { - option("html,${project.name}-${version}.html") + option("markdown,${project.name}-${version}.md") } create("openapiv2") } diff --git a/service/src/main/proto/engine/book/common/service/v1/error.proto b/service/src/main/proto/engine/book/common/service/v1/error.proto new file mode 100644 index 0000000..2b943ab --- /dev/null +++ b/service/src/main/proto/engine/book/common/service/v1/error.proto @@ -0,0 +1,87 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.common.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/actions.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/common/service"; +option java_package = "com.engine.book.common.v1.service"; +option java_multiple_files = true; + +/** + * An error indicating that the [Offer] is no longer available. This is likely due to the [Offer] being sold out. + */ +message OfferNoLongerAvailableError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Common_Service_OfferNoLongerAvailableError_v1" + } + }; +} + +/** + * An error indicating that the requested [Booking] is not in a valid state to perform the operation. + */ +message InvalidStateError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Common_Service_InvalidStateError_v1" + } + }; +} + +/** + * An error that indicates a [Booking] failed in an unknown state and needs to be manually reviewed to determine if it was completed successfully. + */ +message BookingNeedsReviewError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Common_Service_BookingNeedsReviewError_v1" + } + }; +} + +/** + * An error indicating that an action attempted by the client is unavailable for the given [Booking]. + */ +message ActionNotAvailableError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Common_Service_ActionNotAvailableError_v1" + } + }; + + /** + * The availability of the attempted action. + */ + .engine.common.v1.ActionAvailability action_availability = 1; +} + +/** + * An error indicating that a [Booking] cannot be canceled. + */ +message CannotCancelError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Common_Service_CannotCancelError_v1" + } + }; +} \ No newline at end of file diff --git a/service/src/main/proto/engine/book/lodging/service/v1/book.proto b/service/src/main/proto/engine/book/lodging/service/v1/book.proto new file mode 100644 index 0000000..d2bbfcf --- /dev/null +++ b/service/src/main/proto/engine/book/lodging/service/v1/book.proto @@ -0,0 +1,93 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/book/lodging/v1/guests.proto"; +import "engine/book/lodging/v1/metadata.proto"; +import "engine/book/lodging/v1/booking.proto"; +import "engine/book/common/service/v1/error.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging/service"; +option java_package = "com.engine.book.lodging.v1.service"; +option java_multiple_files = true; + +// A request to purchase and reserve an [Offer]. +message BookRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_BookRequest_v1" + } + }; + + // An `continuation_token` from [LodgingBookingService.ConfirmOffer]. + string continuation_token = 2; + + // The details of the guests staying on the [Reservation]. + // The number of provided RoomGuests entries must match the number of rooms requested during shopping. + // Some Properties may not retain all of the provided guest names and may not honor more than one guest's loyalty per [Booking]. + repeated .engine.book.lodging.v1.RoomGuests guests = 3; + + // Metadata to be stored and retrieved along with this [Booking]. + repeated .engine.book.lodging.v1.BookingMetadata metadata = 4; +} + +// The result of an attempt to purchase and reserve an [Offer]. +message BookResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_BookResponse_v1" + } + }; + + // The recorded details of the [Booking] and [Reservation]. + .engine.book.lodging.v1.BookingDetails details = 1; +} + +/** + * Error details of a failed [LodgingBookingService#Book] call. + */ +message BookError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_BookError_v1" + } + }; + + oneof error { + /** + * Defined when the operation returns with a status of INVALID_ARGUMENT and the requested offer + * is no longer available for sale. + */ + .engine.book.common.service.v1.OfferNoLongerAvailableError offer_no_longer_available = 1; + + /** + * Defined when the operation returns with a status of INVALID_ARGUMENT and the booking associated + * with the given continuation token is not in a valid state, e.g. It has already been booked or needs CS review. + */ + .engine.book.common.service.v1.InvalidStateError invalid_state = 2; + + /** + * Defined when the operation returns with a status of INTERNAL and the booking is in a non-retriable state + * that requires manual review to determine if it was completed successfully. + */ + .engine.book.common.service.v1.BookingNeedsReviewError needs_review = 3; + } +} \ No newline at end of file diff --git a/service/src/main/proto/engine/book/lodging/service/v1/confirm_offer.proto b/service/src/main/proto/engine/book/lodging/service/v1/confirm_offer.proto new file mode 100644 index 0000000..e5de0be --- /dev/null +++ b/service/src/main/proto/engine/book/lodging/service/v1/confirm_offer.proto @@ -0,0 +1,92 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/book/common/service/v1/error.proto"; +import "engine/book/lodging/v1/quote.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging/service"; +option java_package = "com.engine.book.lodging.v1.service"; +option java_multiple_files = true; + +// A request to confirm final pricing and availability of an [Offer]. +message ConfirmOfferRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_ConfirmOfferRequest_v1" + } + }; + + // A `continuation_token` from [engine.shop.lodging.service.v1.LodgingShoppingService#FindBestOffers], [engine.shop.lodging.service.v1.LodgingShoppingService#FindBestOffersStreaming], or [engine.shop.lodging.service.v1.LodgingShoppingService#FindAvailability]. + string continuation_token = 1; + + // The currency code for this request. + // This field specifies the currency in which the requester expects to see prices. + // It must adhere to the ISO 4217 three-character alphabetic currency code ("USD", "EUR", etc.). + // https://www.iso.org/iso-4217-currency-codes.html + string currency_code = 2; +} + +// The result of a price and availability check. +message ConfirmOfferResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_ConfirmOfferRequest_v1" + } + }; + + // An opaque token that may be used to book the stay via [engine.shop.lodging.service.v1.LodgingBookingService#Book]. + // See [LodgingBookingService#ConfirmOffer] + string continuation_token = 1; + + // The full details of the [Offer], Room, Dates of Travel, and any notices required for display during the confirmation process. + .engine.book.lodging.v1.Quote quote = 2; +} + +/** + * Error details of a failed [LodgingBookingService#ConfirmOffer] call. + */ +message ConfirmOfferError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_ConfirmOfferError_v1" + } + }; + + oneof error { + /** + * Defined when the operation returns with a status of INVALID_ARGUMENT and the requested offer + * is no longer available for sale. + */ + .engine.book.common.service.v1.OfferNoLongerAvailableError offer_no_longer_available = 1; + + /** + * Defined when the operation returns with a status of INVALID_ARGUMENT and the booking associated + * with the given continuation token is not in a valid state, e.g. It has already been booked or needs CS review. + */ + .engine.book.common.service.v1.InvalidStateError invalid_state = 2; + } + + /** + * Optionally, the unique identifier for this booking if it is known when the error occurs. + */ + optional string booking_id = 3; +} \ No newline at end of file diff --git a/service/src/main/proto/engine/book/lodging/service/v1/get_bookings.proto b/service/src/main/proto/engine/book/lodging/service/v1/get_bookings.proto new file mode 100644 index 0000000..5a5b597 --- /dev/null +++ b/service/src/main/proto/engine/book/lodging/service/v1/get_bookings.proto @@ -0,0 +1,85 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/book/lodging/v1/guests.proto"; +import "engine/book/lodging/v1/metadata.proto"; +import "engine/book/lodging/v1/booking.proto"; +import "google/rpc/status.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging/service"; +option java_package = "com.engine.book.lodging.v1.service"; +option java_multiple_files = true; + +// A request to retrieve one or more [BookingDetails]. +message GetBookingsRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_GetBookingsRequest_v1" + } + }; + + // The identifiers of the [BookingDetails] to retrieve. + // No more than 50 [BookingDetails] may be requested at a time. + repeated string booking_ids = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + min_items: 1; + max_items: 50; + }];; +} + +// The details of a single [BookingDetails]. +message GetBookingsResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_GetBookingsResponse_v1" + } + }; + + // The requested [BookingDetails] in no particular order. + repeated .engine.book.lodging.v1.BookingDetails bookings = 1; +} + +// Error details of a failed [LodgingBookingService.ConfirmOffer] call. +message GetBookingsError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_GetBookingsError_v1" + } + }; + + // If available, the individual [BookingDetails] that could not be retrieved, in no particular order. + repeated .engine.book.lodging.service.v1.GetBookingError errors = 1; +} + +// Describes a failure to retrieve a single [BookingDetails]. +message GetBookingError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_GetBookingError_v1" + } + }; + + // The id of the [BookingDetails] that could not be retrieved. + string booking_id = 1; + + // If available, the reason the [BookingDetails] could not be retrieved. + .google.rpc.Status error = 2; +} diff --git a/service/src/main/proto/engine/book/lodging/service/v1/get_bookings_streaming.proto b/service/src/main/proto/engine/book/lodging/service/v1/get_bookings_streaming.proto new file mode 100644 index 0000000..5426c44 --- /dev/null +++ b/service/src/main/proto/engine/book/lodging/service/v1/get_bookings_streaming.proto @@ -0,0 +1,70 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "google/rpc/status.proto"; + +import "engine/book/lodging/v1/booking.proto"; +import "engine/book/lodging/service/v1/get_bookings.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging/service"; +option java_package = "com.engine.book.lodging.v1.service"; +option java_multiple_files = true; + +// A request to retrieve a single [BookingDetails]. +message GetBookingsStreamingRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_GetBookingsStreamingRequest_v1" + } + }; + + // The identifier of the [BookingDetails] to retrieve. + oneof request_type { + string booking_id = 1; + } +} + +// A single [BookingDetails] or an error describing why that [BookingDetails] could not be retrieved. +message GetBookingsStreamingResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_GetBookingsStreamingResponse_v1" + } + }; + + oneof response_type { + // A single [BookingDetails]. + .engine.book.lodging.v1.BookingDetails booking_details = 1; + + // Error details describing a failure to retrieve a single [BookingDetails]. + .engine.book.lodging.service.v1.GetBookingError error = 2; + } +} + +// Error details for a failed [LodgingBookingService.GetBookingsStreaming] call. +message GetBookingsStreamingError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_GetBookingsStreamingError_v1" + } + }; +} \ No newline at end of file diff --git a/service/src/main/proto/engine/book/lodging/service/v1/preview_cancellation.proto b/service/src/main/proto/engine/book/lodging/service/v1/preview_cancellation.proto new file mode 100644 index 0000000..05cedca --- /dev/null +++ b/service/src/main/proto/engine/book/lodging/service/v1/preview_cancellation.proto @@ -0,0 +1,77 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/actions.proto"; +import "engine/common/v1/refund.proto"; +import "engine/book/common/service/v1/error.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging/service"; +option java_package = "com.engine.book.lodging.v1.service"; +option java_multiple_files = true; + +// Requests the expected result of a cancellation. +message PreviewCancellationRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_PreviewCancellationRequest_v1" + } + }; + + string booking_id = 1; +} + +// Returns the expected result and availability of a cancellation. +message PreviewCancellationResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_PreviewCancellationResponse_v1" + } + }; + + // Details the means in which a cancellation could occur. + .engine.common.v1.AvailableActions available_actions = 1; + + // Optionally, the refund details if they can be determined. + // If cancellation is not possible or cannot be done through the API, this field will not be set. + // See [ActionAvailability] for more information. + .engine.common.v1.Refund refund = 2; +} + +/** + * Error details of a failed [LodgingBookingService#PreviewCancellation] call. + */ +message PreviewCancellationError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_PreviewCancellationError_v1" + } + }; + + oneof error { + /** + * Defined when the operation returns with a status of INVALID_ARGUMENT and the booking associated + * with the given booking ID is not in a valid state, e.g. It has not been booked, or is + * already cancelled. + */ + .engine.book.common.service.v1.InvalidStateError invalid_state = 1; + } +} \ No newline at end of file diff --git a/service/src/main/proto/engine/book/lodging/service/v1/service.proto b/service/src/main/proto/engine/book/lodging/service/v1/service.proto new file mode 100644 index 0000000..ff6bd90 --- /dev/null +++ b/service/src/main/proto/engine/book/lodging/service/v1/service.proto @@ -0,0 +1,189 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.service.v1; + +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/book/lodging/service/v1/confirm_offer.proto"; +import "engine/book/lodging/service/v1/book.proto"; +import "engine/book/lodging/service/v1/get_bookings.proto"; +import "engine/book/lodging/service/v1/get_bookings_streaming.proto"; +import "engine/book/lodging/service/v1/preview_cancellation.proto"; +import "engine/book/lodging/service/v1/submit_cancellation.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging/service"; +option java_package = "com.engine.book.lodging.v1.service"; +option java_multiple_files = true; + + +// A service to support transactions on offer [Offer] or one or more [BookingDetails]. +service LodgingBookingService { + + // Given a `continuation_token` from [LodgingShoppingService.FindBestOffers], [LodgingShoppingService.FindBestOffersStreaming], or [LodgingShoppingService.FindAvailability], confirm the availability of the offer and retrieve any additional information necessary to Book. + // + // In case of error, the status will contain a [ConfirmOfferError] within the [Status.details] field. + rpc ConfirmOffer(.engine.book.lodging.service.v1.ConfirmOfferRequest) + returns (.engine.book.lodging.service.v1.ConfirmOfferResponse) { + option (google.api.http) = { + post: "/book/v1/lodging/confirm-offer" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Confirm the availability, pricing, and details of an Offer prior to booking." + responses: [ + { + key: "400", + value: { + description: "Error details for gRPC Status of INVALID_ARGUMENT(3)." + schema: { + json_schema: { + ref: ".engine.book.lodging.service.v1.ConfirmOfferError" + } + } + } + } + ] + }; + }; + + // Given a `continuation_token` from [LodgingBookingService.ConfirmOffer], purchase and reserve the Offer. + // + // In case of error, the status will contain a [BookError] within the [Status.details] field. + rpc Book(.engine.book.lodging.service.v1.BookRequest) + returns (.engine.book.lodging.service.v1.BookResponse) { + option (google.api.http) = { + put: "/book/v1/lodging/booking" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Purchase and reserve an Offer." + responses: [ + { + key: "400" + value: { + description: "Error details for gRPC Status of INVALID_ARGUMENT(3)." + schema: { + json_schema: { + ref: ".engine.book.lodging.service.v1.BookError" + } + } + } + } + ] + }; + }; + + // Retrieve multiple [BookingDetails] given provided one or more [BookingDetails.booking_id]. + // + // In case of error, the status will contain a [GetBookingsError] within the [Status.details] field. + rpc GetBookings(.engine.book.lodging.service.v1.GetBookingsRequest) + returns (.engine.book.lodging.service.v1.GetBookingsResponse) { + option (google.api.http) = { + post: "/book/v1/lodging/booking", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Retrieve bookings given the provided Booking IDs." + responses: [ + { + key: "400", + value: { + description: "Error details for gRPC Status of INVALID_ARGUMENT(3)." + schema: { + json_schema: { + ref: ".engine.book.lodging.service.v1.GetBookingsError" + } + } + } + } + ] + }; + }; + + // Stream a set of [BookingDetails] given a set of streamed [BookingDetails.booking_id]. + // + // In case of fatal error that interrupts the stream, the status will contain a [GetBookingsStreamingError] within the [Status.details] field. + // In the case of non-fatal errors (such as an individual booking not found), the error details will be found in the response object. + // + // GetBookingsStreaming is not offered via the HTTP/JSON API. + rpc GetBookingsStreaming(stream .engine.book.lodging.service.v1.GetBookingsStreamingRequest) + returns (stream .engine.book.lodging.service.v1.GetBookingsStreamingResponse) { + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Unavailable via HTTP/JSON API." + deprecated: true + }; + }; + + // Get the availability, means, and expected refund for a booking cancellation. + // + // In case of error, the status will contain a [PreviewCancellationError] within the [Status.details] field. + rpc PreviewCancellation(.engine.book.lodging.service.v1.PreviewCancellationRequest) + returns (.engine.book.lodging.service.v1.PreviewCancellationResponse) { + + option (google.api.http) = { + post: "/book/v1/lodging/booking/preview-cancellation", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Get the availability, means, and expected refund for a booking cancellation." + responses: [ + { + key: "400", + value: { + description: "Error details for gRPC Status of INVALID_ARGUMENT(3)." + schema: { + json_schema: { + ref: ".engine.book.lodging.service.v1.PreviewCancellationError" + } + } + } + } + ] + }; + }; + + // Submit a cancellation for a specific booking. + // + // In case of error, the status will contain a [SubmitCancellationError] within the [Status.details] field. + rpc SubmitCancellation(.engine.book.lodging.service.v1.SubmitCancellationRequest) + returns (.engine.book.lodging.service.v1.SubmitCancellationResponse) { + option (google.api.http) = { + post: "/book/v1/lodging/booking/submit-cancellation", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Attempt to cancel a booking." + responses: [ + { + key: "400", + value: { + description: "Error details for gRPC Status of INVALID_ARGUMENT(3)." + schema: { + json_schema: { + ref: ".engine.book.lodging.service.v1.SubmitCancellationError" + } + } + } + } + ] + }; + }; +} diff --git a/service/src/main/proto/engine/book/lodging/service/v1/submit_cancellation.proto b/service/src/main/proto/engine/book/lodging/service/v1/submit_cancellation.proto new file mode 100644 index 0000000..b515073 --- /dev/null +++ b/service/src/main/proto/engine/book/lodging/service/v1/submit_cancellation.proto @@ -0,0 +1,84 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.book.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/book/lodging/v1/booking.proto"; +import "engine/common/v1/actions.proto"; +import "engine/common/v1/refund.proto"; +import "engine/book/common/service/v1/error.proto"; + +option go_package = "engine.com/engine-partner-api/v1/book/lodging/service"; +option java_package = "com.engine.book.lodging.v1.service"; +option java_multiple_files = true; + +// Requests cancellation of a Booking. +message SubmitCancellationRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_SubmitCancellationRequest_v1" + } + }; + + string booking_id = 1; +} + +// Returns the result of a requested cancellation. +message SubmitCancellationResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_SubmitCancellationResponse_v1" + } + }; + + // The timestamp and refund details. + .engine.book.lodging.v1.CancellationDetails details = 1; +} + +/** + * Error information returned in the error details of failed SubmitCancellation calls. + */ +message SubmitCancellationError { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Book_Lodging_Service_SubmitCancellationError_v1" + } + }; + + oneof error { + /** + * The cancellation action is not available for the booking. + */ + .engine.book.common.service.v1.ActionNotAvailableError action_not_available = 1; + + /** + * Defined when the operation returns with a status of INVALID_ARGUMENT and the + * requested booking is not cancellable. + */ + .engine.book.common.service.v1.CannotCancelError cannot_cancel = 2; + + /** + * Defined when the operation returns with a status of INVALID_ARGUMENT and the booking associated + * with the given booking ID is not in a valid state, e.g. It has not been booked, or is + * already cancelled. + */ + .engine.book.common.service.v1.InvalidStateError invalid_state = 3; + } +} \ No newline at end of file diff --git a/service/src/main/proto/engine/content/service/v1/list_properties.proto b/service/src/main/proto/engine/content/service/v1/list_properties.proto index 79763ae..699a242 100644 --- a/service/src/main/proto/engine/content/service/v1/list_properties.proto +++ b/service/src/main/proto/engine/content/service/v1/list_properties.proto @@ -158,7 +158,6 @@ enum ListPropertiesSortMode { // A `Property` that meets the criteria of a `ContentServiceV1.ListProperties` request. message ResponsiveProperty { - option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { json_schema: { title: "ResponsiveProperty_v1" diff --git a/service/src/main/proto/engine/content/service/v1/service.proto b/service/src/main/proto/engine/content/service/v1/service.proto index 3e439bf..9bb8474 100644 --- a/service/src/main/proto/engine/content/service/v1/service.proto +++ b/service/src/main/proto/engine/content/service/v1/service.proto @@ -1,18 +1,18 @@ -/* - * Copyright 2025 HotelEngine, Inc., d/b/a Engine - * - * 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: - * - * http://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. - */ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// syntax = "proto3"; @@ -26,30 +26,8 @@ option go_package = "engine.com/engine-partner-api/v1/content/service"; option java_package = "com.engine.content.v1.service"; option java_multiple_files = true; -option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { - info: { - title: "Engine Partner API -- Content" - version: "1.0" - contact: { - name: "Engine Partner API" - url: "https://github.com/engine-public/partner-api" - email: "partner-api-support@engine.com" - } - license: { - name: "Apache License Version 2.0" - url: "https://github.com/engine-public/partner-api/blob/main/LICENSE" - } - } - external_docs: { - url: "https://github.com/engine-public/partner-api/docs/README.md" - } - host: "partner-api.engine.com" - schemes: HTTPS - consumes: "application/json" - produces: "application/json" -}; -service ContentServiceV1 { +service ContentService { rpc ListProperties(.engine.content.service.v1.ListPropertiesRequest) returns (.engine.content.service.v1.ListPropertiesResponse) { option (google.api.http) = { diff --git a/service/src/main/proto/engine/shop/lodging/service/v1/find_availability.proto b/service/src/main/proto/engine/shop/lodging/service/v1/find_availability.proto new file mode 100644 index 0000000..3c9184f --- /dev/null +++ b/service/src/main/proto/engine/shop/lodging/service/v1/find_availability.proto @@ -0,0 +1,135 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.shop.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; +import "engine/common/v1/sales_channel.proto"; +import "engine/common/v1/traveler.proto"; +import "engine/shop/lodging/v1/offer.proto"; +import "engine/shop/lodging/v1/room.proto"; + +option go_package = "engine.com/engine-partner-api/v1/shop/lodging/service"; +option java_package = "com.engine.shop.lodging.v1.service"; +option java_multiple_files = true; + +message FindAvailabilityRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindAvailabilityRequest_v1" + } + }; + + oneof type { + // A continuation_token from a [BestOffer]. + // Passing a continuation_token allows you to continue the shopping flow from a BestOffers search with the necessary search context preserved. + string continuation_token = 1; + + // A request to find availability for a [Property] directly, skipping the BestOffers search. + .engine.shop.lodging.service.v1.FindAvailabilityPropertyRequest single_property = 2; + } +} + +// A request to find availability for a single [Property]. +message FindAvailabilityPropertyRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindAvailabilityPropertyRequest_v1" + } + }; + + // The Engine [Property] ID + string property_id = 1; + + // An ISO-8601-compliant date on which you will check in to the property. + // See https://en.wikipedia.org/wiki/ISO_8601#Dates + string check_in_date = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + pattern: "^(?[0-9]{4})-(?(?:0[1-9])|(?:1[0-2]))-(?(?:0[1-9])|(?:[1-2][0-9])|(?:3[0-1]))$" + }]; + + // An ISO-8601-compliant date on which you will check out of the property. + // See https://en.wikipedia.org/wiki/ISO_8601#Dates + string check_out_date = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + pattern: "^(?[0-9]{4})-(?(?:0[1-9])|(?:1[0-2]))-(?(?:0[1-9])|(?:[1-2][0-9])|(?:3[0-1]))$" + }]; + + // The number of rooms to book. + // May not exceed 8 + int32 num_rooms = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + minimum: 1, + maximum: 8, + default: "1", + }]; + + // The 3-character ISO-4217 alphabetic currency code to be used to convey pricing information. + // https://www.iso.org/iso-4217-currency-codes.html + string currency_code = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + pattern: "^[A-Z]{3}$" + }]; + + // Describes the travelers for whom you are searching. + // Changes to the quantity or age of travelers has an impact on the Offer price. + // Failure to properly identify your travelers may incur additional charges at checkin or cancellation of the booking. + repeated .engine.common.v1.AnonymousTraveler travelers = 6; + + // If available, the sales channel for the customer + optional .engine.common.v1.SalesChannel sales_channel = 7; +} + +// A response to a [engine.shop.lodging.service.v1.LodgingSearchService#FindBestOffers] call. +message FindAvailabilityResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindAvailabilityResponse_v1" + } + }; + + // A list of available Offers, grouped by RoomDescriptions. + repeated .engine.shop.lodging.service.v1.RoomGroup room_groups = 1; +} + +// A grouping of bookable Offers, collected by the [RoomDescription] to which they apply. +message RoomGroup { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_RoomGroup_v1" + } + }; + + // A description of the rooms for which the Offer applies. + // For example, "Standard 1 King Bed", "Standard 2 Queen Beds", or "Suite 1 King Bed". + .engine.shop.lodging.v1.RoomDescription room_description = 1; + + // A list of bookable Offers for the room. + repeated .engine.shop.lodging.service.v1.Offer offer = 2; +} + +// A bookable Offer representing a set of amenities and a price for the booking. +message Offer { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_Offer_v1" + } + }; + + // An opaque token that may be passed to [engine.book.lodging.service.v1.LodgingBookingService#ConfirmOffer] to begin the booking process. + string continuation_token = 1; + + // The details of the Offer to book. + .engine.shop.lodging.v1.OfferSummary summary = 2; +} \ No newline at end of file diff --git a/service/src/main/proto/engine/shop/lodging/service/v1/find_best_offers.proto b/service/src/main/proto/engine/shop/lodging/service/v1/find_best_offers.proto new file mode 100644 index 0000000..d5b99b7 --- /dev/null +++ b/service/src/main/proto/engine/shop/lodging/service/v1/find_best_offers.proto @@ -0,0 +1,230 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.shop.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/geo.proto"; +import "engine/common/v1/sales_channel.proto"; +import "engine/common/v1/traveler.proto"; +import "engine/content/v1/lodging/property.proto"; +import "engine/shop/lodging/v1/offer.proto"; + +option go_package = "engine.com/engine-partner-api/v1/shop/lodging/service"; +option java_package = "com.engine.shop.lodging.v1.service"; +option java_multiple_files = true; + +// A request that defines the criteria of a "Best Offers" search. +// See [engine.shop.lodging.service.v1.LodgingSearchService#FindBestOffers] +message FindBestOffersRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindBestOffersRequest_v1", + } + }; + + // The criteria to be used to identify and filter Properties and Offers. + .engine.shop.lodging.service.v1.FindBestOffersRequestCriteria criteria = 1; + + // By default, only Properties that have rooms matching your criteria will be returned. + // This differs from [engine.shop.lodging.service.v1.LodgingSearchService#FindBestOffersStreaming] which returns all considered properties. + // You may wish to set this to true if you are building a shopping experience that indicates availability may differ with different criteria, such as dates, but streaming is not viable. + bool include_properties_with_no_availability = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + default: "false", + }]; + + // The order in which the results will be returned. + .engine.shop.lodging.service.v1.FindBestOffersSortMode sort_mode = 3; +} + + +// Defines the base criteria used in a "Best Offers" search. +message FindBestOffersRequestCriteria { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindBestOffersRequestCriteria_v1", + } + }; + + // Defines the criteria used to identify Candidate properties for which we will retrieve "Best Offers". + oneof inclusion_type { + // Check the availability of up to [max_property_candidates] hotels nearest the center of the provided radius. + .engine.shop.lodging.service.v1.RadiusSearch point_and_radius = 1; + } + + // An ISO-8601-compliant date on which you will check in to the property. + // See https://en.wikipedia.org/wiki/ISO_8601#Dates + string check_in_date = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + pattern: "^(?[0-9]{4})-(?(?:0[1-9])|(?:1[0-2]))-(?(?:0[1-9])|(?:[1-2][0-9])|(?:3[0-1]))$" + }]; + + // An ISO-8601-compliant date on which you will check out of the property. + // See https://en.wikipedia.org/wiki/ISO_8601#Dates + string check_out_date = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + pattern: "^(?[0-9]{4})-(?(?:0[1-9])|(?:1[0-2]))-(?(?:0[1-9])|(?:[1-2][0-9])|(?:3[0-1]))$" + }]; + + // The number of rooms to book. + // May not exceed 8 + int32 num_rooms = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + minimum: 1, + maximum: 8, + default: "1", + }]; + + // The 3-character ISO-4217 alphabetic currency code to be used to convey pricing information. + // https://www.iso.org/iso-4217-currency-codes.html + string currency_code = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + pattern: "^[A-Z]{3}$" + }]; + + // Describes the travelers for whom you are searching. + // Changes to the quantity or age of travelers has an impact on the Offer price. + // Failure to properly identify your travelers may incur additional charges at checkin or cancellation of the booking. + repeated .engine.common.v1.AnonymousTraveler travelers = 6; + + // If available, the sales channel for the customer + optional .engine.common.v1.SalesChannel sales_channel = 7; + + // The maximum number of properties to be considered for Offers. + // Properties with no availability will count against this limit, but may not be returned if there is no availability. + // May not exceed 250. + optional int32 max_property_candidates = 98 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + minimum: 1, + maximum: 250, + default: "100", + }]; +} + + +// Describes the various ways a [engine.shop.lodging.service.v1.LodgingSearchService#FindBestOffers] search maybe sorted. +enum FindBestOffersSortMode { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = { + title: "Shop_Lodging_Service_FindBestOffersSortMode_v1" + }; + + // No specific sort criteria will be used. + FIND_BEST_OFFERS_SORT_MODE_UNSPECIFIED = 0; + + // Properties are returned in order of increasing distance. + // In case of ties, Property names are sorted lexically. + FIND_BEST_OFFERS_SORT_MODE_DISTANCE = 1; +} + + +// A search area in a perfect circle around a central point. +message RadiusSearch { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_RadiusSearch_v1" + } + }; + + // Defines the type of radius search to be executed. + oneof center_point_type { + // Latitude and Longitude coordinates to use as the center point of the search. + .engine.common.v1.GeoPoint coordinates = 1; + } + + // The size of the search area. + // Default: 10 miles + // Minimum: 1 mile (~1.6 kilometers) + // Maximum: 50 miles (~80 kilometers) + .engine.common.v1.Distance radius = 101; +} + + +// The results of a [engine.shop.lodging.service.v1.LodgingSearchService#FindBestOffers] search. +message FindBestOffersResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindBestOffersResponse_v1" + } + }; + + // The full list of offers responsive your search. + repeated .engine.shop.lodging.service.v1.PropertyBestOffer offers = 1; +} + + +// A Property and the BestOffer available for that Property given the provided search criteria. +message PropertyBestOffer { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_PropertyBestOffer_v1" + } + }; + + // Details of the Property for which this [BestOffer] applies. + .engine.shop.lodging.service.v1.ResponsiveProperty property = 1; + + // The details of the available [BestOffer]. + .engine.shop.lodging.service.v1.BestOffer best_offer = 2; +} + + +// A Property that is responsive to the current shopping search. +message ResponsiveProperty { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_ResponsiveProperty_v1" + } + }; + + // The details of the returned property. + .engine.content.v1.lodging.Property property = 1; + + // The distance from the center point of the search area, if appropriate. + .engine.common.v1.Distance distance = 2; +} + + +// The details of the Best Offer available for a property given the search criteria and aggregated data about other Offers that are available at the same property. +// A [BestOffer] is not an exhaustive representation of all amenities and options available at a Property. +// For example, it is possible that a [BestOffer] might indicate no loyalty is available because it cannot be known until full availability has been pulled. +// To retrieve the full availability for a Property, see [engine.shop.lodging.service.v1.LodgingSearchService#FindAvailability]. +message BestOffer { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_BestOffer_v1" + } + }; + + // An opaque token that may be used to see all available Offers for the property, or to book the [best_offer] directly. + // See [engine.shop.lodging.service.v1.LodgingSearchService#FindAvailability] + // See [engine.book.lodging.service.v1.LodgingBookingService#ConfirmOffer] + string continuation_token = 1; + + // The specific details of the Best Offer available for a Property. + .engine.shop.lodging.v1.OfferSummary best_offer = 2; + + // True if it is known that at least one refundable Offer is available for the property. + bool is_refundable_available = 3; + + // True if it is known that at least one Offer is available that earns loyalty points. + bool is_loyalty_available = 4; + + // True if it is known that at least one Offer is available that has free parking included. + // Please note: this value will be false if it is known that the Property offers free parking to all guests. + bool is_free_parking_available = 5; + + // True if it is known that at least one Offer is available that has free breakfast included. + // Please note: this value will be false if it is known that the Property offers free breakfast to all guests. + bool is_free_breakfast_available = 6; +} diff --git a/service/src/main/proto/engine/shop/lodging/service/v1/find_best_offers_streaming.proto b/service/src/main/proto/engine/shop/lodging/service/v1/find_best_offers_streaming.proto new file mode 100644 index 0000000..0d531a5 --- /dev/null +++ b/service/src/main/proto/engine/shop/lodging/service/v1/find_best_offers_streaming.proto @@ -0,0 +1,66 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.shop.lodging.service.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/shop/lodging/service/v1/find_best_offers.proto"; + +option go_package = "engine.com/engine-partner-api/v1/shop/lodging/service"; +option java_package = "com.engine.shop.lodging.v1.service"; +option java_multiple_files = true; + +// A request that defines the criteria of a "Best Offers" streaming search. +// See [engine.shop.lodging.service.v1.LodgingSearchService#FindBestOffersStreaming]. +message FindBestOffersStreamingRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindBestOffersStreamingRequest_v1", + description: "Unavailable in the HTTP/JSON API." + read_only: true, + } + }; + + // The criteria to be used to identify and filter Properties and Offers. + .engine.shop.lodging.service.v1.FindBestOffersRequestCriteria criteria = 1; +} + +// An alternation to support multi-message-type streaming of BestOffers. +message FindBestOffersStreamingResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Service_FindBestOffersStreamingResponse_v1", + description: "Unavailable in the HTTP/JSON API." + read_only: true, + } + }; + + oneof message_type { + // A property that will be used as a candidate for Offer retrieval. + // Not every property sent will have a BestOffer associated with it if there is no availability within your specified parameters. + // While it is possible for Properties and BestOffers to be interleaved, the [ResponsiveProperty] for a [BestOffer] will always be sent and received first. + .engine.shop.lodging.service.v1.ResponsiveProperty property = 1; + + // A [BestOffer] candidate for a Property. + // A [BestOffer] will never be sent for a property before its responsive property is sent. + // A single property may have multiple BestOffers returned if a better offer is encountered after the first has already been sent. + // If multiple BestOffers are received for a single Property, the last always wins. + .engine.shop.lodging.service.v1.BestOffer offer = 2; + } +} \ No newline at end of file diff --git a/service/src/main/proto/engine/shop/lodging/service/v1/service.proto b/service/src/main/proto/engine/shop/lodging/service/v1/service.proto new file mode 100644 index 0000000..e158224 --- /dev/null +++ b/service/src/main/proto/engine/shop/lodging/service/v1/service.proto @@ -0,0 +1,80 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.shop.lodging.service.v1; + +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/shop/lodging/service/v1/find_availability.proto"; +import "engine/shop/lodging/service/v1/find_best_offers.proto"; +import "engine/shop/lodging/service/v1/find_best_offers_streaming.proto"; + +option go_package = "engine.com/engine-partner-api/v1/shop/lodging/service"; +option java_package = "com.engine.shop.lodging.v1.service"; +option java_multiple_files = true; + + +// Service and methods to support shopping for lodging. +service LodgingShoppingService { + // Identifies a set of candidate Properties that match the provided criteria. + // Once the appropriate properties have been identified, the Best Offer available for each property is selected and augmented with the details of other available Offers. + // Best Offers are not exhaustive of all available offers for a property. + rpc FindBestOffers(.engine.shop.lodging.service.v1.FindBestOffersRequest) + returns (.engine.shop.lodging.service.v1.FindBestOffersResponse) { + option (google.api.http) = { + post: "/shop/v1/lodging/best-offers", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Search for the Best Offer available for a set of Properties." + description: "Identifies a set of candidate properties that match the provided criteria. Once the appropriate properties have been identified, the Best Offer available for each property is selected and augmented with the details of other available Offers." + }; + }; + + // Identifies a set of candidate Properties that match the provided criteria. + // All ResponsiveProperties are returned regardless of availability. + // BestOffers for candidate Properties will be returned as soon as they are available. + // It is possible for there to be multiple BestOffers for a single candidate property if we discover a better Offer or additional available amenities after the first is sent. + // The last [BestOffer] received for a ResponsiveProperty always supersedes any previously received Offers. + // + // FindBestOffersStreaming is not offered via the HTTP/JSON API. + rpc FindBestOffersStreaming(.engine.shop.lodging.service.v1.FindBestOffersStreamingRequest) + returns (stream .engine.shop.lodging.service.v1.FindBestOffersStreamingResponse) { + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Unavailable via HTTP/JSON API." + deprecated: true + }; + }; + + // Finds availability for a given property, broken down by individual room type. + // RoomGroups describe the various rooms available to book. + // Offers describe the cheapest [Offer] along various dimensions like refundability and loyalty eligible within the [RoomGroup]. + rpc FindAvailability(.engine.shop.lodging.service.v1.FindAvailabilityPropertyRequest) + returns (.engine.shop.lodging.service.v1.FindAvailabilityResponse) { + option (google.api.http) = { + post: "/shop/v1/lodging/availability", + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Search for Availability at a given Property." + description: "Finds availability for a given property, broken down by individual room type. RoomGroups describe the various rooms available to book. Offers describe the cheapest [Offer] along various dimensions like refundability and loyalty eligible within the [RoomGroup]." + }; + }; +} diff --git a/settings.gradle.kts b/settings.gradle.kts index f1d3d40..6f87337 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,6 +18,9 @@ dependencyResolutionManagement { val grpc = "1.60.0" val grpc_kotlin = "1.4.1" versionCatalogs { + create("buildscript") { + library("gson", "com.google.code.gson:gson:2.13.1") + } create("libs") { version("protoc-gen-openapi", "v2.25.1") val grpc = version("grpc", grpc) diff --git a/shop/lodging/build.gradle.kts b/shop/lodging/build.gradle.kts new file mode 100644 index 0000000..b397892 --- /dev/null +++ b/shop/lodging/build.gradle.kts @@ -0,0 +1,45 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + +plugins { + id("com.google.protobuf") +} + +description = "Models comprising the Lodging Shop functionality in the Engine Partner API." + +dependencies { + api(projects.grpcEcosystemProtocGenOpenapiv2) + api(projects.enginePartnerApiCommon) + api(projects.enginePartnerApiContent) + api(libs.protobuf.java) + api(libs.protobuf.kotlin) +} + +kotlin { + explicitApi = ExplicitApiMode.Disabled +} + +protobuf { + protoc { + artifact = libs.protobuf.protoc.get().toString() + } + plugins { + create("doc") { + artifact = libs.grpc.protoc.genDoc.get().toString() + } + } + generateProtoTasks { + all().forEach { + it.generateDescriptorSet = true + it.descriptorSetOptions.includeImports = true + + it.plugins { + create("doc") { + option("markdown,${project.name}-${version}.md") + } + } + it.builtins { + create("kotlin") + } + } + } +} \ No newline at end of file diff --git a/shop/lodging/src/main/proto/engine/shop/lodging/v1/offer.proto b/shop/lodging/src/main/proto/engine/shop/lodging/v1/offer.proto new file mode 100644 index 0000000..fba0eac --- /dev/null +++ b/shop/lodging/src/main/proto/engine/shop/lodging/v1/offer.proto @@ -0,0 +1,77 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.shop.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/common/v1/amenities.proto"; +import "engine/common/v1/conditions.proto"; +import "engine/common/v1/price.proto"; + +option go_package = "engine.com/engine-partner-api/v1/shop/lodging"; +option java_package = "com.engine.shop.v1.lodging"; +option java_multiple_files = true; + +// A summary of the attributes available to a specific Offer. +message OfferSummary { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_OfferSummary_v1" + } + }; + + // The Engine identifier for the Property to which this Offer belongs. + string property_id = 1; + + // A textual description of the Offer. + string description = 2; + + // The pricing details for the Offer. + .engine.common.v1.PriceWithPerUnit price = 3; + + // Amenities that may only apply to this Offer. + // For example, some Offers may include parking. + .engine.shop.lodging.v1.OfferAmenities offer_amenities = 4; + + // The conditions covering refundability and cancellation. + .engine.common.v1.Conditions conditions = 5; + + // If true, this Offer may earn loyalty points. + bool is_loyalty_eligible = 6; +} + +// OfferAmenities represent the value added by the individual offer to be evaluated against other offers. +// OfferAmenities do not include amenities conferred by the Property. +// For example, if a given Property provides free breakfast to all guests, none of the Offers for that property will have Free Breakfast as an amenity. +message OfferAmenities { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_OfferAmenities_v1" + } + }; + + // Describes the availability of free breakfast for the offer or booking. + .engine.common.v1.AmenityAvailability free_breakfast = 1; + + // Describes the availability of free parking for the offer or booking. + .engine.common.v1.AmenityAvailability free_parking = 2; + + // Textual descriptions for display purposes of assorted amenities of the offer or booking that have not been explicitly hoisted to their own attribute. + repeated string other_amenities = 9999; +} \ No newline at end of file diff --git a/shop/lodging/src/main/proto/engine/shop/lodging/v1/room.proto b/shop/lodging/src/main/proto/engine/shop/lodging/v1/room.proto new file mode 100644 index 0000000..c7cfbc6 --- /dev/null +++ b/shop/lodging/src/main/proto/engine/shop/lodging/v1/room.proto @@ -0,0 +1,66 @@ +// +// Copyright 2025 HotelEngine, Inc., d/b/a Engine +// +// 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: +// +// http://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. +// + +syntax = "proto3"; + +package engine.shop.lodging.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +import "engine/shop/lodging/v1/offer.proto"; + +option go_package = "engine.com/engine-partner-api/v1/shop/lodging"; +option java_package = "com.engine.shop.v1.lodging"; +option java_multiple_files = true; + +// Generalized details of the room to which an Offer applies. +message RoomDescription { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_RoomDescription_v1" + } + }; + + // The title of a room. + // For example, "Standard 1 King Bed", or "Suite 1 King Bed", "The Canary Suite". + string title = 1; + + // A textual marketing description of the room. + // For example, "SKYLINE VIEW KING ROOM -CITY OR RIVER VIEW 1 KING -430 SQF" + string description = 2; + + // The available bedding for the room. + repeated .engine.shop.lodging.v1.Bedding beds = 3; + + // URIs to photos of this room. + repeated string photos = 4; +} + +// A description of the quantity and type of beds in the room. +message Bedding { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Shop_Lodging_Bedding_v1" + } + }; + + // The number of this bed type in the room. + int32 count = 1; + + // A textual description of the type of this bed in the room. + // For example, "KING", "QUEEN", or "SLEEPER". + string description = 2; +}