diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 000000000..49de7a8da --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,499 @@ +# Expedia Group Java SDK Foundations - Complete Documentation + +## Overview + +The **Expedia Group Java SDK Foundations** is a comprehensive toolkit for building and maintaining Java/Kotlin SDKs that interact with Expedia Group's platform APIs. This repository provides the core infrastructure, tools, and components needed to generate, build, and publish production-ready SDKs for various Expedia Group services. + +## ๐Ÿ—๏ธ Architecture + +The repository is organized into several key components: + +### Core Components + +``` +expediagroup-java-sdk/ +โ”œโ”€โ”€ core/ # Core SDK libraries and utilities +โ”œโ”€โ”€ generator/ # SDK generation tools +โ”‚ โ””โ”€โ”€ openapi/ # OpenAPI-based code generator +โ”œโ”€โ”€ release/ # Generated SDK releases +โ”‚ โ”œโ”€โ”€ rapid/ # Rapid SDK implementation +โ”‚ โ””โ”€โ”€ fraudPreventionV2/ # Fraud Prevention V2 SDK +โ”œโ”€โ”€ sdk-test/ # Shared testing framework +โ””โ”€โ”€ .github/workflows/ # Reusable CI/CD workflows +``` + +### 1. SDK Core (`/core`) +The foundation layer providing: +- **Base client classes** (`BaseRapidClient`, `ExpediaGroupClient`, `BaseXapClient`) +- **Authentication strategies** (Bearer token, Basic auth, Signature-based) +- **HTTP client configuration** with OkHttp integration +- **Error handling and exception mapping** +- **Logging, serialization, and plugin architecture** +- **Configuration management** + +### 2. SDK Generator (`/generator`) +OpenAPI-based code generation toolkit: +- **Mustache templates** for consistent code generation +- **Custom Kotlin/Java generators** extending OpenAPI tooling +- **Model and operation generation** from OpenAPI specs +- **Documentation generation** capabilities + +### 3. Release SDKs (`/release`) +Generated, production-ready SDKs: +- **Rapid SDK**: Hotel booking and management APIs +- **Fraud Prevention V2 SDK**: Fraud detection services + +## ๐Ÿš€ Quick Start + +### Prerequisites + +- **Java 8+** (JDK 8 or later) +- **Maven 3.8.0+** (or use included Maven wrapper) +- **OpenAPI 3.0+ specification** for your target API + +### Building the Foundation + +```bash +# Clone the repository +git clone https://github.com/ExpediaGroup/expediagroup-java-sdk.git +cd expediagroup-java-sdk + +# Build core components +./mvnw clean install +``` + +### Generating a New SDK + +```bash +# Build the generator +cd generator/openapi +mvn clean install + +# Generate SDK (creates sample in target/sdk) +../../mvnw exec:java + +# Build the generated SDK +cd target/sdk +../../../mvnw clean install +``` + +## ๐Ÿ“š Usage Guide + +### Using Generated SDKs + +#### 1. Add SDK Dependency + +**Maven:** +```xml + + com.expediagroup + rapid-sdk + 5.0.1 + +``` + +**Gradle:** +```groovy +implementation 'com.expediagroup:rapid-sdk:5.0.1' +``` + +#### 2. Initialize the Client + +```kotlin +// Create Rapid client +val rapidClient = RapidClient.builder() + .key("your-api-key") + .secret("your-api-secret") + .endpoint("https://test.ean.com/v3") // or production endpoint + .build() +``` + +#### 3. Execute Operations + +The SDKs use an **operation-based pattern** where each API call is represented as an operation: + +```kotlin +// Search for available properties +val availabilityOperation = GetAvailabilityOperationContext.builder() + .checkin("2024-12-01") + .checkout("2024-12-03") + .occupancy(listOf("2")) + .currency("USD") + .language("en-US") + .countryCode("US") + .propertyId(listOf("12345")) + .customerIp("192.168.1.1") + .build() + +val response = rapidClient.execute(availabilityOperation) +val properties = response.data // List +``` + +```kotlin +// Create a booking +val bookingRequest = CreateItineraryRequest.builder() + .affiliateReferenceId("your-ref-123") + .hold(false) + .email("customer@example.com") + .phone(PhoneRequest.builder() + .countryCode("1") + .areaCode("555") + .number("1234567") + .build()) + .rooms(listOf( + CreateItineraryRequestRoom.builder() + .occupancy(CreateItineraryRequestRoomOccupancy.builder() + .numberOfAdults(2) + .build()) + .ratePlanId("rate-plan-123") + .build() + )) + .build() + +val itineraryOperation = PostItineraryOperation( + PostItineraryOperationParams.builder() + .customerIp("192.168.1.1") + .customerSessionId("session-123") + .build(), + bookingRequest +) + +val bookingResponse = rapidClient.execute(itineraryOperation) +val itinerary = bookingResponse.data // ItineraryCreation +``` + +#### 4. Retrieve Reservations + +```kotlin +// Get reservation details +val reservationOperation = GetReservationByItineraryIdOperation( + GetReservationByItineraryIdOperationParams.builder() + .itineraryId("12345") + .customerIp("192.168.1.1") + .include(listOf( + GetReservationByItineraryIdOperationParams.Include.HISTORY, + GetReservationByItineraryIdOperationParams.Include.LINKS + )) + .build() +) + +val reservationResponse = rapidClient.execute(reservationOperation) +val reservation = reservationResponse.data // Itinerary +``` + +### Asynchronous Operations + +All operations support async execution: + +```kotlin +val futureResponse = rapidClient.executeAsync(availabilityOperation) +futureResponse.thenAccept { response -> + val properties = response.data + // Process properties +} +``` + +### Error Handling + +The SDKs use typed exceptions for different error scenarios: + +```kotlin +try { + val response = rapidClient.execute(operation) + // Success handling +} catch (e: ExpediaGroupApiErrorException) { + // API returned an error response + val errorResponse = e.errorResponse + when (errorResponse) { + is RapidError -> { + // Handle Rapid-specific errors + println("Error: ${errorResponse.errors}") + } + } +} catch (e: ExpediaGroupServiceException) { + // Service-level errors (network, timeout, etc.) + println("Service error: ${e.message}") +} catch (e: ExpediaGroupConfigurationException) { + // Configuration errors (missing keys, etc.) + println("Configuration error: ${e.message}") +} +``` + +### Builder Pattern + +All request objects and operations use the builder pattern: + +```kotlin +val propertyContentParams = GetPropertyContentOperationParams.builder() + .propertyId(listOf("12345", "67890")) + .language("en-US") + .supply_source("expedia") + .include(listOf( + GetPropertyContentOperationParams.Include.THEMES, + GetPropertyContentOperationParams.Include.AMENITIES + )) + .build() +``` + +### Response Handling + +Responses are wrapped in a `Response` object: + +```kotlin +val response = rapidClient.execute(operation) + +// Access response data +val data = response.data + +// Access response metadata +val headers = response.headers +val statusCode = response.statusCode +val transactionId = response.transactionId +``` + +### Configuration Options + +#### Basic Configuration + +```kotlin +val client = RapidClient.builder() + .key("api-key") + .secret("api-secret") + .endpoint("https://api.ean.com/v3") + .requestTimeout(30000) // 30 seconds + .connectionTimeout(10000) // 10 seconds + .socketTimeout(60000) // 60 seconds + .build() +``` + +#### Advanced Configuration + +```kotlin +// Custom OkHttpClient +val customOkHttpClient = OkHttpClient.Builder() + .connectionPool(ConnectionPool(10, 5, TimeUnit.MINUTES)) + .addInterceptor(customInterceptor) + .build() + +val client = RapidClient.builder() + .key("api-key") + .secret("api-secret") + .httpClient(customOkHttpClient) + .maskedLoggingHeaders(setOf("Authorization", "X-API-Key")) + .maskedLoggingBodyFields(setOf("password", "creditCard")) + .build() +``` + +### Environment Configuration + +SDKs support multiple environments: + +```kotlin +// Test environment +val testClient = RapidClient.builder() + .key("test-key") + .secret("test-secret") + .endpoint("https://test.ean.com/v3") + .build() + +// Production environment +val prodClient = RapidClient.builder() + .key("prod-key") + .secret("prod-secret") + .endpoint("https://api.ean.com/v3") + .build() +``` + +## ๐Ÿ”ง Customization and Extension + +### Custom Authentication + +Implement custom authentication strategies: + +```kotlin +class CustomAuthStrategy : AuthenticationStrategy { + override fun loadAuth(auth: Auth) { + // Custom auth logic + } + + override fun getAuthorizationHeader(): String { + // Return custom auth header + } +} +``` + +### Custom HTTP Interceptors + +Add custom request/response processing: + +```kotlin +val customClient = OkHttpClient.Builder() + .addInterceptor { chain -> + val request = chain.request().newBuilder() + .addHeader("X-Custom-Header", "value") + .build() + chain.proceed(request) + } + .build() +``` + +## ๐Ÿ”Œ SDK Generation + +### Generating Custom SDKs + +1. **Prepare OpenAPI Specification** + - Ensure your API has a valid OpenAPI 3.0+ specification + - Use [Spec Transformer](https://github.com/ExpediaGroup/spec-transformer) if needed + +2. **Configure Generation** + ```bash + cd generator/openapi + # Modify generation configuration as needed + ../../mvnw exec:java + ``` + +3. **Customize Templates** + - Templates are located in `generator/openapi/src/main/resources/templates/` + - Modify Mustache templates for custom code generation + +4. **Build Generated SDK** + ```bash + cd target/sdk + ../../../mvnw clean install + ``` + +### Publishing SDKs + +Use the provided GitHub Actions workflows: + +1. **Manual Release**: `.github/workflows/manual-release.yml` +2. **Automated Publishing**: Configurable for Maven Central +3. **Documentation Generation**: Integrated Dokka support + +## ๐Ÿงช Testing + +### Unit Testing + +```kotlin +// Test with mock responses +@Test +fun testGetAvailability() { + val mockClient = createMockRapidClient() + val operation = GetAvailabilityOperationContext.builder() + .checkin("2024-12-01") + .checkout("2024-12-03") + .build() + + val response = mockClient.execute(operation) + assertThat(response.data).isNotEmpty() +} +``` + +### Integration Testing + +```kotlin +// Test against sandbox environment +@Test +@IntegrationTest +fun testRealApiCall() { + val client = RapidClient.builder() + .key(System.getenv("TEST_API_KEY")) + .secret(System.getenv("TEST_API_SECRET")) + .endpoint("https://test.ean.com/v3") + .build() + + // Perform real API calls +} +``` + +## ๐Ÿ” Best Practices + +### 1. Resource Management +```kotlin +// Use try-with-resources for file operations +response.data.use { file -> + // Process file content +} +``` + +### 2. Error Handling +```kotlin +// Always handle specific exceptions +try { + val response = client.execute(operation) + return response.data +} catch (e: ExpediaGroupApiErrorException) { + logger.error("API error: ${e.errorResponse}") + throw BusinessException("Booking failed", e) +} catch (e: ExpediaGroupServiceException) { + logger.error("Service error: ${e.message}") + throw ServiceUnavailableException("Service temporarily unavailable", e) +} +``` + +### 3. Configuration Management +```kotlin +// Use configuration objects for reusable settings +class ApiConfiguration { + companion object { + fun createTestClient() = RapidClient.builder() + .key(System.getenv("TEST_API_KEY")) + .secret(System.getenv("TEST_API_SECRET")) + .endpoint("https://test.ean.com/v3") + .requestTimeout(30000) + .build() + } +} +``` + +### 4. Logging +```kotlin +// Configure structured logging +val client = RapidClient.builder() + .key("api-key") + .secret("api-secret") + .maskedLoggingHeaders(setOf("Authorization")) + .maskedLoggingBodyFields(setOf("creditCard", "ssn")) + .build() +``` + +## ๐Ÿ“– Available SDKs + +| SDK | Description | Maven Central | Documentation | +|-----|-------------|---------------|---------------| +| **Rapid SDK** | Hotel booking, availability, and management | [![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/rapid-sdk)](https://central.sonatype.com/artifact/com.expediagroup/rapid-sdk) | [Rapid SDK Docs](https://developers.expediagroup.com/docs/products/rapid/sdk/java) | +| **EWS XAP SDK** | Expedia Web Services XML API | [![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/xap-sdk)](https://central.sonatype.com/artifact/com.expediagroup/xap-sdk) | [XAP SDK Docs](https://developers.expediagroup.com/xap/sdk) | +| **Fraud Prevention V2** | Fraud detection and prevention | [![Maven Central](https://img.shields.io/maven-central/v/com.expediagroup/fraudpreventionv2-sdk)](https://central.sonatype.com/artifact/com.expediagroup/fraudpreventionv2-sdk) | [Fraud Prevention Docs](https://developers.expediagroup.com/docs/products/fraud-prevention/v2) | + +## ๐Ÿค Contributing + +1. **Fork the repository** +2. **Create a feature branch**: `git checkout -b feature/amazing-feature` +3. **Make changes and add tests** +4. **Commit changes**: `git commit -m 'Add amazing feature'` +5. **Push to branch**: `git push origin feature/amazing-feature` +6. **Create Pull Request** + +See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines. + +## ๐Ÿ“ License + +This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. + +## ๐Ÿ†˜ Support + +- **Repository Issues**: [GitHub Issues](https://github.com/ExpediaGroup/expediagroup-java-sdk/issues) +- **SDK-Specific Support**: Refer to individual SDK repositories +- **Developer Hub**: [Expedia Group Developer Portal](https://developers.expediagroup.com/) + +## ๐Ÿ”„ Version History + +| Version | Release Date | Key Changes | +|---------|-------------|-------------| +| 5.0.1 | Latest | Current stable release | +| 5.0.0 | 2024 | Major version with breaking changes | +| 4.x.x | 2023 | Previous stable series | + +--- + +**Made with โค๏ธ by the Expedia Group Platform Team** diff --git a/SDK_GENERATION_GUIDE.md b/SDK_GENERATION_GUIDE.md new file mode 100644 index 000000000..7dd262178 --- /dev/null +++ b/SDK_GENERATION_GUIDE.md @@ -0,0 +1,656 @@ +# SDK Generation Guide + +## Overview + +This guide explains how to use the Expedia Group Java SDK Foundations to generate custom SDKs from OpenAPI specifications. + +## ๐ŸŽฏ Who This Guide Is For + +- **API Providers** who want to generate Java/Kotlin SDKs for their APIs +- **Platform Teams** building internal SDKs +- **Developers** extending existing SDKs with custom functionality + +## ๐Ÿ—๏ธ SDK Generation Architecture + +### Core Components + +1. **OpenAPI Generator**: Extended OpenAPI code generator with custom templates +2. **Mustache Templates**: Customizable templates for code generation +3. **Core Libraries**: Shared utilities and base classes +4. **Build System**: Maven-based build and publishing pipeline + +### Generated SDK Structure + +``` +generated-sdk/ +โ”œโ”€โ”€ src/main/kotlin/ +โ”‚ โ”œโ”€โ”€ client/ # SDK client classes +โ”‚ โ”‚ โ””โ”€โ”€ MyApiClient.kt # Main client implementation +โ”‚ โ”œโ”€โ”€ models/ # Data models +โ”‚ โ”‚ โ”œโ”€โ”€ Request*.kt # Request objects +โ”‚ โ”‚ โ”œโ”€โ”€ Response*.kt # Response objects +โ”‚ โ”‚ โ””โ”€โ”€ *.kt # Domain models +โ”‚ โ””โ”€โ”€ operations/ # Operation definitions +โ”‚ โ”œโ”€โ”€ *Operation.kt # Operation classes +โ”‚ โ”œโ”€โ”€ *OperationParams.kt # Parameter classes +โ”‚ โ””โ”€โ”€ *OperationContext.kt # Context classes +โ”œโ”€โ”€ docs/ # Generated documentation +โ””โ”€โ”€ pom.xml # Maven configuration +``` + +## ๐Ÿš€ Getting Started + +### Prerequisites + +1. **OpenAPI Specification** (3.0+) for your API +2. **Java 8+** development environment +3. **Maven 3.8+** build tool + +### Step 1: Prepare Your OpenAPI Spec + +Ensure your OpenAPI specification follows best practices: + +```yaml +openapi: 3.0.3 +info: + title: My API + version: 1.0.0 + description: Custom API for demonstration +servers: + - url: https://api.example.com/v1 + description: Production server + - url: https://test-api.example.com/v1 + description: Test server + +paths: + /hotels/search: + post: + operationId: searchHotels + summary: Search for hotels + parameters: + - name: X-Customer-IP + in: header + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SearchRequest' + responses: + '200': + description: Search results + content: + application/json: + schema: + $ref: '#/components/schemas/SearchResponse' + +components: + schemas: + SearchRequest: + type: object + required: + - destination + - checkin + - checkout + properties: + destination: + type: string + description: Destination city or property ID + checkin: + type: string + format: date + description: Check-in date (YYYY-MM-DD) + checkout: + type: string + format: date + description: Check-out date (YYYY-MM-DD) + guests: + type: integer + minimum: 1 + maximum: 8 + default: 2 + description: Number of guests + + SearchResponse: + type: object + properties: + hotels: + type: array + items: + $ref: '#/components/schemas/Hotel' + totalResults: + type: integer + + Hotel: + type: object + properties: + id: + type: string + description: Unique hotel identifier + name: + type: string + description: Hotel name + location: + $ref: '#/components/schemas/Location' + pricePerNight: + $ref: '#/components/schemas/Price' + + Location: + type: object + properties: + city: + type: string + country: + type: string + coordinates: + $ref: '#/components/schemas/Coordinates' + + Coordinates: + type: object + properties: + latitude: + type: number + format: double + longitude: + type: number + format: double + + Price: + type: object + properties: + amount: + type: number + format: double + currency: + type: string + pattern: '^[A-Z]{3}$' + + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + +security: + - ApiKeyAuth: [] +``` + +### Step 2: Configure the Generator + +1. **Clone the repository**: + ```bash + git clone https://github.com/ExpediaGroup/expediagroup-java-sdk.git + cd expediagroup-java-sdk + ``` + +2. **Place your OpenAPI spec**: + ```bash + cp your-api-spec.yaml generator/openapi/src/main/resources/specs/ + ``` + +3. **Configure generation parameters**: + + Edit `generator/openapi/pom.xml` to customize generation: + + ```xml + + org.springframework.boot + spring-boot-maven-plugin + + + + --input-spec=src/main/resources/specs/your-api-spec.yaml + + + --output=target/sdk + + + --package-name=com.example.sdk.myapi + --client-name=MyApiClient + --sdk-version=1.0.0 + + + --base-url=https://api.example.com/v1 + --auth-type=API_KEY + + + + ``` + +### Step 3: Generate the SDK + +```bash +# Build the generator +cd generator/openapi +mvn clean install + +# Generate your SDK +../../mvnw exec:java + +# Build the generated SDK +cd target/sdk +../../../mvnw clean install +``` + +### Step 4: Test the Generated SDK + +The generator creates a basic test structure: + +```kotlin +// Generated client usage +val client = MyApiClient.builder() + .key("your-api-key") + .endpoint("https://test-api.example.com/v1") + .build() + +// Search for hotels +val searchRequest = SearchRequest.builder() + .destination("New York") + .checkin("2024-12-01") + .checkout("2024-12-03") + .guests(2) + .build() + +val searchParams = SearchHotelsOperationParams.builder() + .customerIp("192.168.1.1") + .build() + +val operation = SearchHotelsOperation(searchParams, searchRequest) +val response = client.execute(operation) + +response.data.hotels?.forEach { hotel -> + println("Hotel: ${hotel.name} - ${hotel.pricePerNight?.amount} ${hotel.pricePerNight?.currency}") +} +``` + +## ๐ŸŽจ Customization + +### Custom Templates + +The generator uses Mustache templates located in `generator/openapi/src/main/resources/templates/`: + +``` +templates/ +โ”œโ”€โ”€ client.mustache # Main client class +โ”œโ”€โ”€ model.mustache # Data model classes +โ”œโ”€โ”€ operation.mustache # Operation classes +โ”œโ”€โ”€ operationParams.mustache # Parameter classes +โ”œโ”€โ”€ pom.mustache # Maven POM file +โ””โ”€โ”€ README.mustache # SDK documentation +``` + +#### Customizing the Client Template + +Edit `templates/client.mustache`: + +```mustache +package {{package}}.client + +import {{package}}.models.* +import {{package}}.operations.* +import com.expediagroup.sdk.core.client.BaseExpediaGroupClient +import com.expediagroup.sdk.core.configuration.ExpediaGroupClientConfiguration + +/** + * {{appName}} SDK Client + * {{appDescription}} + * + * Generated on: {{generatedDate}} + * Version: {{sdkVersion}} + */ +class {{classname}} private constructor( + clientConfiguration: ExpediaGroupClientConfiguration +) : BaseExpediaGroupClient("{{packageName}}", clientConfiguration) { + + class Builder : BaseExpediaGroupClient.Builder() { + override fun build() = {{classname}}( + ExpediaGroupClientConfiguration( + key, secret, endpoint, requestTimeout, + connectionTimeout, socketTimeout, + maskedLoggingHeaders, maskedLoggingBodyFields + ) + ) + } + + companion object { + @JvmStatic fun builder() = Builder() + } + + {{#operations}} + {{#operation}} + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}} + {{/allParams}} + * @throws ExpediaGroupApiErrorException + * @return {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}EmptyResponse{{/returnType}} + */ + fun execute(operation: {{classname}}Operation): {{#returnType}}Response<{{returnType}}>{{/returnType}}{{^returnType}}EmptyResponse{{/returnType}} { + return {{#returnType}}execute<{{operationIdCamelCase}}Request, {{returnType}}>(operation){{/returnType}}{{^returnType}}executeWithEmptyResponse<{{operationIdCamelCase}}Request>(operation){{/returnType}} + } + + {{/operation}} + {{/operations}} +} +``` + +### Custom Model Generation + +Edit `templates/model.mustache` for custom model structures: + +```mustache +package {{package}}.models + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +{{#imports}} +import {{import}} +{{/imports}} + +/** + * {{description}} + {{#vars}} + * @param {{name}} {{description}} + {{/vars}} + */ +@JsonDeserialize(builder = {{classname}}.Builder::class) +data class {{classname}}( +{{#vars}} + @get:JsonProperty("{{baseName}}") + val {{name}}: {{dataType}}{{#hasDefaultValue}} = {{{defaultValue}}}{{/hasDefaultValue}}{{^hasDefaultValue}}{{#isNullable}}? = null{{/isNullable}}{{/hasDefaultValue}}{{#hasMore}},{{/hasMore}} +{{/vars}} +) { + companion object { + fun builder() = Builder() + } + + class Builder { + {{#vars}} + @JsonProperty("{{baseName}}") + private var {{name}}: {{dataType}}{{#isNullable}}?{{/isNullable}} = {{#hasDefaultValue}}{{{defaultValue}}}{{/hasDefaultValue}}{{^hasDefaultValue}}{{#isNullable}}null{{/isNullable}}{{^isNullable}}TODO("Required field"){{/isNullable}}{{/hasDefaultValue}} + + fun {{name}}({{name}}: {{dataType}}{{#isNullable}}?{{/isNullable}}) = apply { this.{{name}} = {{name}} } + {{/vars}} + + fun build(): {{classname}} { + return {{classname}}( + {{#vars}} + {{name}} = {{name}}{{#hasMore}},{{/hasMore}} + {{/vars}} + ) + } + } +} +``` + +### Advanced Configuration + +#### Custom Authentication + +For APIs with custom authentication, modify the generator configuration: + +```yaml +# In your generation config +authType: CUSTOM +authTemplate: | + override fun getAuthenticationStrategy(): AuthenticationStrategy { + return MyCustomAuthStrategy(configurationProvider) + } +``` + +#### Custom Error Handling + +Add custom error mapping: + +```yaml +errorMapping: + 400: BadRequestException + 401: UnauthorizedException + 403: ForbiddenException + 404: NotFoundException + 500: InternalServerErrorException +``` + +### Step 5: Publishing Your SDK + +#### Configure Maven for Publishing + +Update the generated `pom.xml`: + +```xml +com.example +myapi-sdk +1.0.0 +My API SDK +SDK for My API +https://github.com/example/myapi-sdk + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + Your Name + your.email@example.com + Your Organization + + + + + scm:git:git@github.com:example/myapi-sdk.git + scm:git:git@github.com:example/myapi-sdk.git + https://github.com/example/myapi-sdk + +``` + +#### GitHub Actions for CI/CD + +Create `.github/workflows/publish.yml`: + +```yaml +name: Publish SDK +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 8 + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'temurin' + + - name: Build and publish + run: | + mvn clean install + mvn deploy + env: + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} +``` + +## ๐Ÿงช Testing Generated SDKs + +### Unit Testing Template + +The generator creates a testing framework: + +```kotlin +@ExtendWith(MockitoExtension::class) +class MyApiClientTest { + + @Mock + private lateinit var httpClient: HttpClient + + private lateinit var client: MyApiClient + + @BeforeEach + fun setup() { + client = MyApiClient.builder() + .key("test-key") + .secret("test-secret") + .endpoint("https://test-api.example.com/v1") + .build() + } + + @Test + fun `searchHotels should return valid response`() { + // Given + val searchRequest = SearchRequest.builder() + .destination("New York") + .checkin("2024-12-01") + .checkout("2024-12-03") + .build() + + val expectedResponse = SearchResponse.builder() + .hotels(listOf( + Hotel.builder() + .id("hotel-123") + .name("Test Hotel") + .build() + )) + .totalResults(1) + .build() + + // When + val operation = SearchHotelsOperation( + SearchHotelsOperationParams.builder() + .customerIp("192.168.1.1") + .build(), + searchRequest + ) + + val response = client.execute(operation) + + // Then + assertThat(response.data.hotels).hasSize(1) + assertThat(response.data.hotels?.first()?.name).isEqualTo("Test Hotel") + } +} +``` + +### Integration Testing + +```kotlin +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MyApiClientIntegrationTest { + + private lateinit var client: MyApiClient + + @BeforeAll + fun setup() { + client = MyApiClient.builder() + .key(System.getenv("TEST_API_KEY")) + .secret(System.getenv("TEST_API_SECRET")) + .endpoint("https://test-api.example.com/v1") + .build() + } + + @Test + fun `should perform real API call`() { + val searchRequest = SearchRequest.builder() + .destination("New York") + .checkin("2024-12-01") + .checkout("2024-12-03") + .guests(2) + .build() + + val operation = SearchHotelsOperation( + SearchHotelsOperationParams.builder() + .customerIp("192.168.1.1") + .build(), + searchRequest + ) + + val response = client.execute(operation) + + assertThat(response.statusCode).isEqualTo(200) + assertThat(response.data.hotels).isNotNull() + } +} +``` + +## ๐Ÿ“š Best Practices + +### 1. OpenAPI Specification Guidelines + +- **Use descriptive operation IDs**: These become method names +- **Include comprehensive examples**: Used in documentation generation +- **Define reusable components**: Reduces code duplication +- **Use appropriate HTTP status codes**: Enables proper error handling +- **Add detailed descriptions**: Improves generated documentation + +### 2. SDK Design Patterns + +- **Builder pattern**: All request objects use builders for flexibility +- **Operation pattern**: Each API call is represented as an operation object +- **Response wrapping**: Responses include metadata (headers, status codes) +- **Type safety**: Strong typing for all parameters and responses +- **Error handling**: Specific exceptions for different error scenarios + +### 3. Versioning Strategy + +```xml + +1.0.0 + + +2.0.0 +1.1.0 +1.0.1 +``` + +### 4. Documentation Generation + +The generator can create comprehensive documentation: + +```bash +# Generate Dokka documentation +mvn dokka:dokka + +# Generate OpenAPI documentation +mvn exec:java -Dexec.args="--generate-docs" +``` + +## ๐Ÿ”ง Troubleshooting + +### Common Issues + +1. **Generator fails to parse OpenAPI spec**: + - Validate your spec using [Swagger Editor](https://editor.swagger.io/) + - Check for circular references in schemas + - Ensure all `$ref` references are valid + +2. **Generated code doesn't compile**: + - Check for reserved keywords in property names + - Verify all required dependencies are included + - Review custom template syntax + +3. **Authentication not working**: + - Verify security scheme definitions in OpenAPI spec + - Check API key/secret configuration + - Review endpoint URLs (test vs production) + +### Debug Mode + +Enable debug logging during generation: + +```bash +mvn exec:java -Dexec.args="--verbose --debug" +``` + +This comprehensive guide provides everything needed to generate custom SDKs using the Expedia Group Java SDK Foundations. diff --git a/SDK_USAGE_GUIDE.md b/SDK_USAGE_GUIDE.md new file mode 100644 index 000000000..20e2e4376 --- /dev/null +++ b/SDK_USAGE_GUIDE.md @@ -0,0 +1,490 @@ +# SDK Usage Guide + +## Quick Start for SDK Users + +This guide shows you how to use the generated Expedia Group SDKs in your applications. + +## ๐Ÿจ Rapid SDK - Hotel Booking + +### Installation + +```xml + + com.expediagroup + rapid-sdk + 5.0.1 + +``` + +### Basic Setup + +```kotlin +val rapidClient = RapidClient.builder() + .key("your-api-key") + .secret("your-api-secret") + .endpoint("https://test.ean.com/v3") // Test environment + .build() +``` + +### Common Operations + +#### 1. Search for Hotels + +```kotlin +// Search for available properties +val searchParams = GetAvailabilityOperationParams.builder() + .checkin("2024-12-01") + .checkout("2024-12-03") + .occupancy(listOf("2")) // 2 adults + .currency("USD") + .language("en-US") + .countryCode("US") + .propertyId(listOf("12345")) + .customerIp("192.168.1.1") + .build() + +val searchOperation = GetAvailabilityOperation(searchParams) +val response = rapidClient.execute(searchOperation) + +response.data.forEach { property -> + println("Property: ${property.propertyId}") + property.roomTypes?.forEach { room -> + println(" Room: ${room.name} - ${room.ratePlans?.firstOrNull()?.price?.total?.inclusive}") + } +} +``` + +#### 2. Get Property Details + +```kotlin +val contentParams = GetPropertyContentOperationParams.builder() + .propertyId(listOf("12345")) + .language("en-US") + .include(listOf( + GetPropertyContentOperationParams.Include.AMENITIES, + GetPropertyContentOperationParams.Include.IMAGES, + GetPropertyContentOperationParams.Include.THEMES + )) + .build() + +val contentOperation = GetPropertyContentOperation(contentParams) +val contentResponse = rapidClient.execute(contentOperation) + +contentResponse.data.forEach { (propertyId, content) -> + println("Property: ${content.name}") + println("Description: ${content.descriptions?.overview}") + println("Address: ${content.address?.line1}, ${content.address?.city}") + + content.images?.forEach { image -> + println("Image: ${image.url}") + } +} +``` + +#### 3. Create a Booking + +```kotlin +// Build the booking request +val guestInfo = CreateItineraryRequestRoomsGuestInfo.builder() + .firstName("John") + .lastName("Doe") + .build() + +val room = CreateItineraryRequestRoom.builder() + .occupancy(CreateItineraryRequestRoomOccupancy.builder() + .numberOfAdults(2) + .build()) + .ratePlanId("12345-rate-plan") + .guestInfo(listOf(guestInfo)) + .build() + +val bookingRequest = CreateItineraryRequest.builder() + .affiliateReferenceId("your-booking-ref-${System.currentTimeMillis()}") + .hold(false) // false = book immediately, true = hold reservation + .email("john.doe@example.com") + .phone(PhoneRequest.builder() + .countryCode("1") + .areaCode("555") + .number("1234567") + .build()) + .rooms(listOf(room)) + .payments(listOf( + PaymentRequest.builder() + .type(PaymentRequest.Type.CUSTOMER_CARD) + .number("4111111111111111") + .expirationMonth("12") + .expirationYear("2025") + .securityCode("123") + .build() + )) + .build() + +val bookingParams = PostItineraryOperationParams.builder() + .customerIp("192.168.1.1") + .customerSessionId("session-${System.currentTimeMillis()}") + .build() + +val bookingOperation = PostItineraryOperation(bookingParams, bookingRequest) + +try { + val bookingResponse = rapidClient.execute(bookingOperation) + val itinerary = bookingResponse.data + + println("Booking successful!") + println("Itinerary ID: ${itinerary.itineraryId}") + println("Confirmation Number: ${itinerary.confirmationId}") + + itinerary.rooms?.forEach { room -> + println("Room ${room.id}: ${room.confirmationId}") + } + +} catch (e: ExpediaGroupApiErrorException) { + println("Booking failed: ${e.message}") + // Handle specific error scenarios + when (val error = e.errorResponse) { + is RapidError -> { + error.errors?.forEach { err -> + println("Error: ${err.type} - ${err.message}") + } + } + } +} +``` + +#### 4. Retrieve Existing Booking + +```kotlin +val retrieveParams = GetReservationByItineraryIdOperationParams.builder() + .itineraryId("12345678") + .customerIp("192.168.1.1") + .include(listOf( + GetReservationByItineraryIdOperationParams.Include.HISTORY, + GetReservationByItineraryIdOperationParams.Include.LINKS + )) + .build() + +val retrieveOperation = GetReservationByItineraryIdOperation(retrieveParams) +val reservationResponse = rapidClient.execute(retrieveOperation) + +val reservation = reservationResponse.data +println("Booking Status: ${reservation.bookingStatus}") +println("Total Price: ${reservation.totalAmount}") + +reservation.rooms?.forEach { room -> + println("Room: ${room.ratePlans?.firstOrNull()?.description}") + println("Check-in: ${room.checkIn}") + println("Check-out: ${room.checkOut}") +} +``` + +#### 5. Cancel a Booking + +```kotlin +val cancelParams = DeleteRoomOperationParams.builder() + .itineraryId("12345678") + .roomId("room-123") + .customerIp("192.168.1.1") + .reason("customer_request") + .build() + +val cancelOperation = DeleteRoomOperation(cancelParams) + +try { + rapidClient.execute(cancelOperation) + println("Room cancelled successfully") +} catch (e: ExpediaGroupApiErrorException) { + println("Cancellation failed: ${e.message}") +} +``` + +### Advanced Features + +#### Pagination + +```kotlin +// Use paginator for large result sets +val params = GetRegionsOperationParams.builder() + .language("en-US") + .include(listOf(GetRegionsOperationParams.Include.STANDARD)) + .build() + +val paginator = rapidClient.paginate(GetRegionsOperation(params)) + +for (page in paginator) { + page.data.forEach { region -> + println("Region: ${region.name}") + } +} +``` + +#### Async Operations + +```kotlin +val futureResponse = rapidClient.executeAsync(searchOperation) + +futureResponse.thenAccept { response -> + response.data.forEach { property -> + println("Found property: ${property.propertyId}") + } +}.exceptionally { throwable -> + println("Search failed: ${throwable.message}") + null +} +``` + +#### Custom HTTP Configuration + +```kotlin +val customClient = OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor { chain -> + val request = chain.request().newBuilder() + .addHeader("X-My-Custom-Header", "value") + .build() + chain.proceed(request) + } + .build() + +val rapidClient = RapidClient.builder() + .key("api-key") + .secret("api-secret") + .httpClient(customClient) + .build() +``` + +## ๐Ÿ›ก๏ธ Fraud Prevention SDK + +### Installation + +```xml + + com.expediagroup + fraudpreventionv2-sdk + 2.0.0 + +``` + +### Basic Usage + +```kotlin +val fraudClient = FraudPreventionV2Client.builder() + .key("your-api-key") + .secret("your-api-secret") + .endpoint("https://api.expediagroup.com/fraudpreventionv2") + .build() + +// Submit account screen event +val accountScreen = AccountScreenRequest.builder() + .siteInfo(SiteInfo.builder() + .brandName("YourBrand") + .locale("en-US") + .build()) + .deviceDetails(DeviceDetails.builder() + .ipAddress("192.168.1.1") + .userAgent("Mozilla/5.0...") + .build()) + .accountDetails(AccountDetails.builder() + .userId("user123") + .username("john.doe@example.com") + .build()) + .build() + +val params = AccountScreenNotifyOperationParams.builder() + .build() + +val operation = AccountScreenNotifyOperation(params, accountScreen) +fraudClient.execute(operation) +``` + +## ๐Ÿ”ง Error Handling Patterns + +### Comprehensive Error Handling + +```kotlin +fun bookHotel(bookingRequest: CreateItineraryRequest): BookingResult { + return try { + val response = rapidClient.execute(bookingOperation) + BookingResult.Success(response.data) + + } catch (e: ExpediaGroupApiErrorException) { + when (val error = e.errorResponse) { + is RapidError -> { + val firstError = error.errors?.firstOrNull() + when (firstError?.type) { + "ROOM_UNAVAILABLE" -> BookingResult.RoomUnavailable + "PAYMENT_FAILED" -> BookingResult.PaymentFailed(firstError.message) + "INVALID_INPUT" -> BookingResult.InvalidInput(firstError.message) + else -> BookingResult.ApiError(e.message ?: "Unknown error") + } + } + else -> BookingResult.ApiError(e.message ?: "Unknown error") + } + } catch (e: ExpediaGroupServiceException) { + BookingResult.ServiceUnavailable + } catch (e: ExpediaGroupConfigurationException) { + BookingResult.ConfigurationError(e.message ?: "Configuration error") + } catch (e: Exception) { + BookingResult.UnknownError(e.message ?: "Unknown error") + } +} + +sealed class BookingResult { + data class Success(val itinerary: ItineraryCreation) : BookingResult() + object RoomUnavailable : BookingResult() + data class PaymentFailed(val reason: String?) : BookingResult() + data class InvalidInput(val message: String?) : BookingResult() + data class ApiError(val message: String) : BookingResult() + object ServiceUnavailable : BookingResult() + data class ConfigurationError(val message: String) : BookingResult() + data class UnknownError(val message: String) : BookingResult() +} +``` + +## ๐Ÿ—๏ธ Builder Pattern Examples + +All SDK objects use the builder pattern for clean, type-safe construction: + +```kotlin +// Complex nested object construction +val searchRequest = GetAvailabilityOperationParams.builder() + .checkin("2024-12-01") + .checkout("2024-12-05") + .occupancy(listOf("2", "1-9")) // 2 adults, 1 adult + 1 child age 9 + .currency("USD") + .language("en-US") + .countryCode("US") + .propertyId(listOf("12345", "67890")) + .rateOption(listOf( + GetAvailabilityOperationParams.RateOption.MEMBER, + GetAvailabilityOperationParams.RateOption.NET_RATES + )) + .salesChannel("website") + .salesEnvironment("hotel_only") + .filter(listOf( + GetAvailabilityOperationParams.Filter.REFUNDABLE, + GetAvailabilityOperationParams.Filter.PROPERTY_COLLECTBOOLEAN + )) + .include(listOf( + GetAvailabilityOperationParams.Include.AMENITIES, + GetAvailabilityOperationParams.Include.SALE_SCENARIO + )) + .build() +``` + +## ๐Ÿ“Š Response Processing + +### Extracting Data from Responses + +```kotlin +val response = rapidClient.execute(searchOperation) + +// Response metadata +println("Status: ${response.statusCode}") +println("Transaction ID: ${response.transactionId}") +response.headers.forEach { (key, values) -> + println("Header $key: ${values.joinToString(", ")}") +} + +// Process the actual data +response.data.forEach { property -> + // Property details + println("Property: ${property.propertyId}") + println("Name: ${property.name}") + + // Availability and pricing + property.roomTypes?.forEach { roomType -> + println(" Room Type: ${roomType.name}") + + roomType.ratePlans?.forEach { ratePlan -> + println(" Rate Plan: ${ratePlan.id}") + println(" Price: ${ratePlan.price?.total?.inclusive} ${ratePlan.price?.total?.currency}") + + // Cancellation policy + ratePlan.cancellationPolicy?.let { policy -> + println(" Cancellation: ${policy.type}") + } + } + } + + // Property amenities + property.amenities?.forEach { amenity -> + println(" Amenity: ${amenity.name}") + } +} +``` + +## ๐Ÿ”„ Configuration Management + +### Environment-Based Configuration + +```kotlin +class RapidClientFactory { + companion object { + fun createClient(environment: Environment): RapidClient { + return when (environment) { + Environment.TEST -> RapidClient.builder() + .key(System.getenv("RAPID_TEST_KEY")) + .secret(System.getenv("RAPID_TEST_SECRET")) + .endpoint("https://test.ean.com/v3") + .requestTimeout(30000) + .build() + + Environment.PRODUCTION -> RapidClient.builder() + .key(System.getenv("RAPID_PROD_KEY")) + .secret(System.getenv("RAPID_PROD_SECRET")) + .endpoint("https://api.ean.com/v3") + .requestTimeout(10000) + .build() + } + } + } + + enum class Environment { + TEST, PRODUCTION + } +} +``` + +### Spring Boot Integration + +```kotlin +@Configuration +class SdkConfiguration { + + @Bean + @Primary + fun rapidClient( + @Value("\${rapid.api.key}") apiKey: String, + @Value("\${rapid.api.secret}") apiSecret: String, + @Value("\${rapid.api.endpoint}") endpoint: String + ): RapidClient { + return RapidClient.builder() + .key(apiKey) + .secret(apiSecret) + .endpoint(endpoint) + .requestTimeout(30000) + .connectionTimeout(10000) + .socketTimeout(60000) + .build() + } +} + +@Service +class HotelService(private val rapidClient: RapidClient) { + + fun searchHotels(searchCriteria: SearchCriteria): List { + val params = GetAvailabilityOperationParams.builder() + .checkin(searchCriteria.checkin) + .checkout(searchCriteria.checkout) + .occupancy(searchCriteria.occupancy) + .customerIp(searchCriteria.customerIp) + .build() + + val operation = GetAvailabilityOperation(params) + return rapidClient.execute(operation).data + } +} +``` + +This usage guide provides practical examples and patterns for using the generated SDKs effectively in real applications.