tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 699483ebb73900e1394e3f55331ce4e6b4dba863
parent 310af0b4f5620c270c0f86a195745c1eb4946d3c
Author: Jeff Boek <j@jboek.com>
Date:   Fri, 17 Oct 2025 11:18:08 +0000

Bug 1978563 - Adds ability to query an engine for the structure of an address for a given locale r=android-reviewers,sfamisa

Differential Revision: https://phabricator.services.mozilla.com/D268054

Diffstat:
Mmobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt | 12++++++++++++
Amobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/RuntimeAddressStructureAccessor.kt | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt | 11+++++++++++
Amobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/autofill/RuntimeAddressStructureAccessorTest.kt | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/Engine.kt | 9+++++++--
Amobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/autofill/AddressStructure.kt | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/autofill/AddressStructureRuntime.kt | 30++++++++++++++++++++++++++++++
7 files changed, 609 insertions(+), 2 deletions(-)

diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt @@ -14,6 +14,8 @@ import mozilla.components.ExperimentalAndroidComponentsApi import mozilla.components.browser.engine.fission.GeckoWebContentIsolationMapper.intoWebContentIsolationStrategy import mozilla.components.browser.engine.gecko.activity.GeckoActivityDelegate import mozilla.components.browser.engine.gecko.activity.GeckoScreenOrientationDelegate +import mozilla.components.browser.engine.gecko.autofill.DefaultRuntimeAddressStructureAccessor +import mozilla.components.browser.engine.gecko.autofill.RuntimeAddressStructureAccessor import mozilla.components.browser.engine.gecko.ext.getAntiTrackingPolicy import mozilla.components.browser.engine.gecko.ext.getEtpCategory import mozilla.components.browser.engine.gecko.ext.getEtpLevel @@ -49,6 +51,7 @@ import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.Settings import mozilla.components.concept.engine.activity.ActivityDelegate import mozilla.components.concept.engine.activity.OrientationDelegate +import mozilla.components.concept.engine.autofill.AddressStructure import mozilla.components.concept.engine.content.blocking.TrackerLog import mozilla.components.concept.engine.content.blocking.TrackingProtectionExceptionStorage import mozilla.components.concept.engine.fission.WebContentIsolationStrategy @@ -110,6 +113,7 @@ class GeckoEngine( GeckoTrackingProtectionExceptionStorage(runtime), private val geckoPreferenceAccessor: GeckoPreferenceAccessor = DefaultGeckoPreferenceAccessor(), private val runtimeTranslationAccessor: RuntimeTranslationAccessor = DefaultRuntimeTranslationAccessor(), + private val addressStructureAccessor: RuntimeAddressStructureAccessor = DefaultRuntimeAddressStructureAccessor(), ) : Engine, WebExtensionRuntime, TranslationsRuntime, BrowserPreferencesRuntime { private val executor by lazy { executorProvider.invoke() } private val localeUpdater = LocaleSettingUpdater(context, runtime) @@ -1214,6 +1218,14 @@ class GeckoEngine( ) } + override fun getAddressStructure( + countryCode: String, + onSuccess: (AddressStructure) -> Unit, + onError: (Throwable) -> Unit, + ) { + addressStructureAccessor.getAddressStructure(countryCode, onSuccess, onError) + } + /** * See [Engine.profiler]. */ diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/RuntimeAddressStructureAccessor.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/autofill/RuntimeAddressStructureAccessor.kt @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.browser.engine.gecko.autofill + +import mozilla.components.concept.engine.autofill.AddressStructure +import mozilla.components.concept.engine.autofill.UnexpectedNullError +import org.mozilla.geckoview.GeckoResult +import org.mozilla.geckoview.Autocomplete.AddressStructure as GeckoAddressStructure + +/** + * Interface for fetching the address structure for a given country. + */ +fun interface RuntimeAddressStructureAccessor { + /** + * Fetch the address structure for a given country. + * + * @param countryCode the country code used when fetching an address structure. + * @param onSuccess Callback invoked with a list of [AddressField]'s + * @param onError Callback invoked if the check fails or an error occurs. + */ + fun getAddressStructure( + countryCode: String, + onSuccess: (AddressStructure) -> Unit, + onError: (Throwable) -> Unit, + ) +} + +internal class DefaultRuntimeAddressStructureAccessor( + private val getGeckoAddressStructure: (String) -> GeckoResult<List<GeckoAddressStructure.Field>> = { + GeckoAddressStructure.getAddressStructure(it) + }, +) : RuntimeAddressStructureAccessor { + override fun getAddressStructure( + countryCode: String, + onSuccess: (AddressStructure) -> Unit, + onError: (Throwable) -> Unit, + ) { + handleGeckoResult( + geckoResult = getGeckoAddressStructure(countryCode).toConceptAddressStructure(), + onSuccess = onSuccess, + onError = onError, + ) + } + + private fun <T : Any> handleGeckoResult( + geckoResult: GeckoResult<T>, + onSuccess: (T) -> Unit, + onError: (Throwable) -> Unit, + ) { + geckoResult.then( + { res: T? -> + if (res != null) { + onSuccess(res) + } else { + onError(UnexpectedNullError()) + } + GeckoResult<Void>() + }, + { throwable -> + onError(throwable) + GeckoResult<Void>() + }, + ) + } + + private fun GeckoResult<List<GeckoAddressStructure.Field>>.toConceptAddressStructure() = map { results -> + if (results == null) { + return@map null + } + + val fields = results.mapNotNull { result -> + val id = AddressStructure.Field.ID.from(result.id) + val localizationKey = AddressStructure.Field.LocalizationKey.from(result.localizationKey) + when (result) { + is GeckoAddressStructure.Field.SelectField -> AddressStructure.Field.SelectField( + id = id, + localizationKey = localizationKey, + defaultSelectionKey = result.defaultValue, + options = result.options.map { option -> + AddressStructure.Field.SelectField.Option( + key = option.key, + value = option.value, + ) + }, + ) + + is GeckoAddressStructure.Field.TextField -> AddressStructure.Field.TextField( + id = id, + localizationKey = localizationKey, + ) + + else -> null + } + } + + return@map AddressStructure(fields) + } +} diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt @@ -10,6 +10,7 @@ import android.graphics.Color import android.os.Looper.getMainLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.ExperimentalAndroidComponentsApi +import mozilla.components.browser.engine.gecko.autofill.RuntimeAddressStructureAccessor import mozilla.components.browser.engine.gecko.ext.getAntiTrackingPolicy import mozilla.components.browser.engine.gecko.mediaquery.toGeckoValue import mozilla.components.browser.engine.gecko.preferences.GeckoPreferenceAccessor @@ -4482,6 +4483,16 @@ class GeckoEngineTest { assert(!onErrorCalled) { "Should not have called an error." } } + @Test + fun `WHEN getAddressStructure is called THEN addressStructureAccessor should be called`() { + var getAddressStructureCalled = false + val engine = GeckoEngine(testContext, runtime = runtime, addressStructureAccessor = { region, success, error -> + getAddressStructureCalled = true + }) + engine.getAddressStructure("JP", { _ -> }, { _ -> }) + assertTrue("AddressStructureAccessor should be called,", getAddressStructureCalled) + } + private fun createSocialTrackersLogEntryList(): List<ContentBlockingController.LogEntry> { val blockedLogEntry = object : ContentBlockingController.LogEntry() {} diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/autofill/RuntimeAddressStructureAccessorTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/autofill/RuntimeAddressStructureAccessorTest.kt @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +package mozilla.components.browser.engine.gecko.autofill + +import mozilla.components.concept.engine.autofill.AddressStructure +import mozilla.components.concept.engine.autofill.UnexpectedNullError +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.geckoview.Autocomplete +import org.mozilla.geckoview.GeckoResult +import org.robolectric.RobolectricTestRunner +import org.robolectric.shadows.ShadowLooper + +@RunWith(RobolectricTestRunner::class) +class RuntimeAddressStructureAccessorTest { + @Test + fun `GIVEN a RuntimeAddressStructureAccessor WHEN we get a valid GeckoResult THEN map the values to concept-engine fields `() { + val addressStructureAccessor = DefaultRuntimeAddressStructureAccessor { + GeckoResult.fromValue( + listOf( + Autocomplete.AddressStructure.Field.TextField("name", "autofill-address-name"), + Autocomplete.AddressStructure.Field.TextField("organization", "autofill-address-organization"), + Autocomplete.AddressStructure.Field.TextField("street-address", "autofill-address-street-address"), + Autocomplete.AddressStructure.Field.TextField("address-level1", "autofill-address-state"), + Autocomplete.AddressStructure.Field.TextField("address-level2", "autofill-address-city"), + Autocomplete.AddressStructure.Field.TextField("address-level3", "autofill-address-prefecture"), + Autocomplete.AddressStructure.Field.TextField("postal-code", "autofill-address-postal-code"), + Autocomplete.AddressStructure.Field.TextField("tel", "autofill-address-tel"), + Autocomplete.AddressStructure.Field.TextField("email", "autofill-address-email"), + Autocomplete.AddressStructure.Field.TextField("unknown", "unknown-localization"), + Autocomplete.AddressStructure.Field.SelectField( + "country", + "autofill-address-country", + "key-1", + listOf(Autocomplete.AddressStructure.Field.SelectField.Option("key-1", "value-1")), + ), + ), + ) + } + + var structure: AddressStructure? = null + addressStructureAccessor.getAddressStructure( + "US", + { structure = it }, + { throwable -> }, + ) + + val expectedStructure = AddressStructure( + listOf( + AddressStructure.Field.TextField(AddressStructure.Field.ID.Name, AddressStructure.Field.LocalizationKey.Name), + AddressStructure.Field.TextField(AddressStructure.Field.ID.Organization, AddressStructure.Field.LocalizationKey.Organization), + AddressStructure.Field.TextField(AddressStructure.Field.ID.StreetAddress, AddressStructure.Field.LocalizationKey.StreetAddress), + AddressStructure.Field.TextField(AddressStructure.Field.ID.AddressLevel1, AddressStructure.Field.LocalizationKey.State), + AddressStructure.Field.TextField(AddressStructure.Field.ID.AddressLevel2, AddressStructure.Field.LocalizationKey.City), + AddressStructure.Field.TextField(AddressStructure.Field.ID.AddressLevel3, AddressStructure.Field.LocalizationKey.Prefecture), + AddressStructure.Field.TextField(AddressStructure.Field.ID.PostalCode, AddressStructure.Field.LocalizationKey.PostalCode), + AddressStructure.Field.TextField(AddressStructure.Field.ID.Tel, AddressStructure.Field.LocalizationKey.Tel), + AddressStructure.Field.TextField(AddressStructure.Field.ID.Email, AddressStructure.Field.LocalizationKey.Email), + AddressStructure.Field.TextField(AddressStructure.Field.ID.Unknown("unknown"), AddressStructure.Field.LocalizationKey.Unknown("unknown-localization")), + AddressStructure.Field.SelectField( + AddressStructure.Field.ID.Country, + AddressStructure.Field.LocalizationKey.Country, + "key-1", + listOf(AddressStructure.Field.SelectField.Option("key-1", "value-1")), + ), + ), + ) + + ShadowLooper.idleMainLooper() + assertEquals(expectedStructure, structure) + } + + @Test + fun `GIVEN a RuntimeAddressStructureAccessor WHEN we get a valid GeckoResult containing null THEN call onError`() { + val addressStructureAccessor = DefaultRuntimeAddressStructureAccessor { + GeckoResult.fromValue(null) + } + + var throwable: Throwable? = null + addressStructureAccessor.getAddressStructure( + "US", + { _ -> }, + { throwable = it }, + ) + + ShadowLooper.idleMainLooper() + assertTrue("Throwable should be UnexpectedNullError", throwable is UnexpectedNullError) + } + + @Test + fun `GIVEN a RuntimeAddressStructureAccessor WHEN we get a valid GeckoResult containing a throwable THEN call onError`() { + val addressStructureAccessor = DefaultRuntimeAddressStructureAccessor { + GeckoResult.fromException(IllegalStateException()) + } + + var throwable: Throwable? = null + addressStructureAccessor.getAddressStructure( + "US", + { _ -> }, + { throwable = it }, + ) + + ShadowLooper.idleMainLooper() + assertTrue("Throwable should be IllegalStateException", throwable is IllegalStateException) + } +} diff --git a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/Engine.kt b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/Engine.kt @@ -12,6 +12,7 @@ import androidx.annotation.MainThread import mozilla.components.concept.base.profiler.Profiler import mozilla.components.concept.engine.activity.ActivityDelegate import mozilla.components.concept.engine.activity.OrientationDelegate +import mozilla.components.concept.engine.autofill.AddressStructureRuntime import mozilla.components.concept.engine.content.blocking.TrackerLog import mozilla.components.concept.engine.content.blocking.TrackingProtectionExceptionStorage import mozilla.components.concept.engine.preferences.BrowserPreferencesRuntime @@ -27,8 +28,12 @@ import org.json.JSONObject /** * Entry point for interacting with the engine implementation. */ -interface Engine : WebExtensionRuntime, TranslationsRuntime, BrowserPreferencesRuntime, DataCleanable { - +interface Engine : + WebExtensionRuntime, + TranslationsRuntime, + BrowserPreferencesRuntime, + AddressStructureRuntime, + DataCleanable { /** * Describes a combination of browsing data types stored by the engine. */ diff --git a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/autofill/AddressStructure.kt b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/autofill/AddressStructure.kt @@ -0,0 +1,340 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.engine.autofill + +/** + * Represents the visual structure of an address. + */ +data class AddressStructure(val fields: List<Field>) { + /** + * Represents a single field in an [AddressStructure] + */ + sealed class Field { + abstract val id: ID + abstract val localizationKey: LocalizationKey + + /** + * Represents the ID of a [Field]. This ID maps 1:1 to a property on [Address]. + */ + sealed class ID(val id: String) { + + /** + * ID representing the Name property + **/ + object Name : ID("name") + + /** + * ID representing the Organization property + **/ + object Organization : ID("organization") + + /** + * ID representing the StreetAddress property + **/ + object StreetAddress : ID("street-address") + + /** + * ID representing the AddressLevel1 property + **/ + object AddressLevel1 : ID("address-level1") + + /** + * ID representing the AddressLevel2 property + **/ + object AddressLevel2 : ID("address-level2") + + /** + * ID representing the AddressLevel3 property + **/ + object AddressLevel3 : ID("address-level3") + + /** + * ID representing the PostalCode property + **/ + object PostalCode : ID("postal-code") + + /** + * ID representing the Country property + **/ + object Country : ID("country") + + /** + * ID representing the Tel property + **/ + object Tel : ID("tel") + + /** + * ID representing the Email property + **/ + object Email : ID("email") + + /** + * ID representing an unknown property + **/ + data class Unknown(val value: String) : ID(value) + + /** + * Companion object for ID + */ + companion object { + /** + * Retrieve an ID with a string + * + * @param key used to lookup an ID. + */ + fun from(key: String) = when (key) { + "name" -> Name + "organization" -> Organization + "street-address" -> StreetAddress + "address-level1" -> AddressLevel1 + "address-level2" -> AddressLevel2 + "address-level3" -> AddressLevel3 + "postal-code" -> PostalCode + "country" -> Country + "tel" -> Tel + "email" -> Email + else -> Unknown(key) + } + } + } + + /** + * Represents the localizationKey of a [Field]. This is used to lookup a user facing string + * in the client. + */ + sealed class LocalizationKey(val key: String) { + + /** + * Localization Key for Name + **/ + object Name : LocalizationKey("autofill-address-name") + + /** + * Localization Key for Organization + **/ + object Organization : LocalizationKey("autofill-address-organization") + + /** + * Localization Key for StreetAddress + **/ + object StreetAddress : LocalizationKey("autofill-address-street-address") + + /** + * Localization Key for Street + **/ + object Street : LocalizationKey("autofill-address-street") + + /** + * Localization Key for Neighborhood + **/ + object Neighborhood : LocalizationKey("autofill-address-neighborhood") + + /** + * Localization Key for VillageTownship + **/ + object VillageTownship : LocalizationKey("autofill-address-village-township") + + /** + * Localization Key for Island + **/ + object Island : LocalizationKey("autofill-address-island") + + /** + * Localization Key for Townland + **/ + object Townland : LocalizationKey("autofill-address-townland") + + /** + * Localization Key for City + **/ + object City : LocalizationKey("autofill-address-city") + + /** + * Localization Key for District + **/ + object District : LocalizationKey("autofill-address-district") + + /** + * Localization Key for PostTown + **/ + object PostTown : LocalizationKey("autofill-address-post-town") + + /** + * Localization Key for Suburb + **/ + object Suburb : LocalizationKey("autofill-address-suburb") + + /** + * Localization Key for Province + **/ + object Province : LocalizationKey("autofill-address-province") + + /** + * Localization Key for State + **/ + object State : LocalizationKey("autofill-address-state") + + /** + * Localization Key for County + **/ + object County : LocalizationKey("autofill-address-county") + + /** + * Localization Key for Parish + **/ + object Parish : LocalizationKey("autofill-address-parish") + + /** + * Localization Key for Prefecture + **/ + object Prefecture : LocalizationKey("autofill-address-prefecture") + + /** + * Localization Key for Area + **/ + object Area : LocalizationKey("autofill-address-area") + + /** + * Localization Key for DoSi + **/ + object DoSi : LocalizationKey("autofill-address-do-si") + + /** + * Localization Key for Department + **/ + object Department : LocalizationKey("autofill-address-department") + + /** + * Localization Key for Emirate + **/ + object Emirate : LocalizationKey("autofill-address-emirate") + + /** + * Localization Key for Oblast + **/ + object Oblast : LocalizationKey("autofill-address-oblast") + + /** + * Localization Key for Pin + **/ + object Pin : LocalizationKey("autofill-address-pin") + + /** + * Localization Key for PostalCode + **/ + object PostalCode : LocalizationKey("autofill-address-postal-code") + + /** + * Localization Key for Zip + **/ + object Zip : LocalizationKey("autofill-address-zip") + + /** + * Localization Key for Eircode + **/ + object Eircode : LocalizationKey("autofill-address-eircode") + + /** + * Localization Key for Country + **/ + object Country : LocalizationKey("autofill-address-country") + + /** + * Localization Key for CountryOnly + **/ + object CountryOnly : LocalizationKey("autofill-address-country-only") + + /** + * Localization Key for Tel + **/ + object Tel : LocalizationKey("autofill-address-tel") + + /** + * Localization Key for Email + **/ + object Email : LocalizationKey("autofill-address-email") + + /** + * Unknown Localization Key + **/ + data class Unknown(val value: String) : LocalizationKey(value) + + /** + * Companion object for LocalizationKey + */ + companion object { + /** + * Retrieve a [LocalizationKey] with a string + * + * @param key used to lookup a [LocalizationKey]. + */ + fun from(key: String) = when (key) { + "autofill-address-name" -> Name + "autofill-address-organization" -> Organization + "autofill-address-street-address" -> StreetAddress + "autofill-address-street" -> Street + "autofill-address-neighborhood" -> Neighborhood + "autofill-address-village-township" -> VillageTownship + "autofill-address-island" -> Island + "autofill-address-townland" -> Townland + "autofill-address-city" -> City + "autofill-address-district" -> District + "autofill-address-post-town" -> PostTown + "autofill-address-suburb" -> Suburb + "autofill-address-province" -> Province + "autofill-address-state" -> State + "autofill-address-county" -> County + "autofill-address-parish" -> Parish + "autofill-address-prefecture" -> Prefecture + "autofill-address-area" -> Area + "autofill-address-do-si" -> DoSi + "autofill-address-department" -> Department + "autofill-address-emirate" -> Emirate + "autofill-address-oblast" -> Oblast + "autofill-address-pin" -> Pin + "autofill-address-postal-code" -> PostalCode + "autofill-address-zip" -> Zip + "autofill-address-eircode" -> Eircode + "autofill-address-country" -> Country + "autofill-address-country-only" -> CountryOnly + "autofill-address-tel" -> Tel + "autofill-address-email" -> Email + else -> Unknown(key) + } + } + } + + /** + * Text input address field + */ + data class TextField( + override val id: ID, + override val localizationKey: LocalizationKey, + ) : Field() + + /** + * Select address field + * + * @param id Identifier for the field. Used to map this to the right prompt value + * @param localizationKey key used to lookup the displayable string for this field. + * @param defaultSelectionKey The key for the default value. Ideally this key would be represented in [options]. + * @param options List of [Option] options that the selector field represents. + */ + data class SelectField( + override val id: ID, + override val localizationKey: LocalizationKey, + val defaultSelectionKey: String, + val options: List<Option>, + ) : Field() { + /** + * An option item of a [SelectField] + * + * @param key The key to identify the elements + * @param value The value if the address field option + */ + data class Option(val key: String, val value: String) + } + } +} diff --git a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/autofill/AddressStructureRuntime.kt b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/autofill/AddressStructureRuntime.kt @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.engine.autofill + +/** + * Error that is returned if we got a success value out of a [GeckoResult] that is null. + */ +class UnexpectedNullError : IllegalStateException("Expected address structure, got null") + +/** + * Runtime interface for address metadata + */ +interface AddressStructureRuntime { + + /** + * Gets the supported address fields for a country. This is useful when constructing the + * address input or edit functionality. + * + * @param countryCode Country code (2 letter variant) for the current country selection. + * @param onSuccess Callback invoked when the address structure has been retreived. + * @param onError Callback invoked in event of an error. + */ + fun getAddressStructure( + countryCode: String, + onSuccess: (AddressStructure) -> Unit, + onError: (Throwable) -> Unit, + ): Unit = onError(UnsupportedOperationException("getAddressFormLayout is not yet supported")) +}