-
-
Notifications
You must be signed in to change notification settings - Fork 358
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Multiplatform OSM API client (#5686)
- Loading branch information
1 parent
8d488a0
commit 6090c26
Showing
83 changed files
with
2,849 additions
and
1,627 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
app/src/main/java/de/westnordost/streetcomplete/data/ApiClientExceptions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package de.westnordost.streetcomplete.data | ||
|
||
import io.ktor.client.plugins.ClientRequestException | ||
import io.ktor.client.plugins.ServerResponseException | ||
import io.ktor.http.HttpStatusCode | ||
import kotlinx.io.IOException | ||
import kotlinx.serialization.SerializationException | ||
|
||
inline fun <T> wrapApiClientExceptions(block: () -> T): T = | ||
try { | ||
block() | ||
} | ||
// server replied with (server) error 5xx | ||
catch (e: ServerResponseException) { | ||
throw ConnectionException(e.message, e) | ||
} | ||
// unexpected answer by server -> server issue | ||
catch (e: SerializationException) { | ||
throw ConnectionException(e.message, e) | ||
} | ||
// issue with establishing a connection -> nothing we can do about | ||
catch (e: IOException) { | ||
throw ConnectionException(e.message, e) | ||
} | ||
// server replied with (client) error 4xx | ||
catch (e: ClientRequestException) { | ||
when (e.response.status) { | ||
// request timeout is rather a temporary connection error | ||
HttpStatusCode.RequestTimeout -> { | ||
throw ConnectionException(e.message, e) | ||
} | ||
// rate limiting is treated like a temporary connection error, i.e. try again later | ||
HttpStatusCode.TooManyRequests -> { | ||
throw ConnectionException(e.message, e) | ||
} | ||
// authorization is something we can handle (by requiring (re-)login of the user) | ||
HttpStatusCode.Forbidden, HttpStatusCode.Unauthorized -> { | ||
throw AuthorizationException(e.message, e) | ||
} | ||
else -> { | ||
throw ApiClientException(e.message, e) | ||
} | ||
} | ||
} | ||
|
||
/** The server responded with an unhandled error code */ | ||
class ApiClientException(message: String? = null, cause: Throwable? = null) | ||
: RuntimeException(message, cause) | ||
|
||
/** An error occurred while trying to communicate with an API over the internet. Either the | ||
* connection with the API cannot be established, the server replies with a server error (5xx), | ||
* request timeout (408) or it responds with an unexpected response, i.e. an error occurs while | ||
* parsing the response. */ | ||
class ConnectionException(message: String? = null, cause: Throwable? = null) | ||
: RuntimeException(message, cause) | ||
|
||
/** While posting an update to an API over the internet, the API reports that our data is based on | ||
* outdated data */ | ||
class ConflictException(message: String? = null, cause: Throwable? = null) | ||
: RuntimeException(message, cause) | ||
|
||
/** When a query made on an API over an internet would (probably) return a too large result */ | ||
class QueryTooBigException (message: String? = null, cause: Throwable? = null) | ||
: RuntimeException(message, cause) | ||
|
||
/** An error that indicates that the user either does not have the necessary authorization or | ||
* authentication to execute an action through an API over the internet. */ | ||
class AuthorizationException(message: String? = null, cause: Throwable? = null) | ||
: RuntimeException(message, cause) |
29 changes: 0 additions & 29 deletions
29
app/src/main/java/de/westnordost/streetcomplete/data/CommunicationException.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 0 additions & 6 deletions
6
app/src/main/java/de/westnordost/streetcomplete/data/download/QueryTooBigException.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
...java/de/westnordost/streetcomplete/data/osm/edits/upload/changesets/ChangesetApiClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package de.westnordost.streetcomplete.data.osm.edits.upload.changesets | ||
|
||
import de.westnordost.streetcomplete.data.AuthorizationException | ||
import de.westnordost.streetcomplete.data.ConflictException | ||
import de.westnordost.streetcomplete.data.ConnectionException | ||
import de.westnordost.streetcomplete.data.user.UserLoginSource | ||
import de.westnordost.streetcomplete.data.wrapApiClientExceptions | ||
import io.ktor.client.HttpClient | ||
import io.ktor.client.call.body | ||
import io.ktor.client.plugins.ClientRequestException | ||
import io.ktor.client.plugins.expectSuccess | ||
import io.ktor.client.request.bearerAuth | ||
import io.ktor.client.request.put | ||
import io.ktor.client.request.setBody | ||
import io.ktor.http.HttpStatusCode | ||
|
||
class ChangesetApiClient( | ||
private val httpClient: HttpClient, | ||
private val baseUrl: String, | ||
private val userLoginSource: UserLoginSource, | ||
private val serializer: ChangesetApiSerializer, | ||
) { | ||
/** | ||
* Open a new changeset with the given tags | ||
* | ||
* @param tags tags of this changeset. Usually it is comment and source. | ||
* | ||
* @throws AuthorizationException if the application does not have permission to edit the map | ||
* (OAuth scope "write_api") | ||
* @throws ConnectionException if a temporary network connection problem occurs | ||
* | ||
* @return the id of the changeset | ||
*/ | ||
suspend fun open(tags: Map<String, String>): Long = wrapApiClientExceptions { | ||
val response = httpClient.put(baseUrl + "changeset/create") { | ||
userLoginSource.accessToken?.let { bearerAuth(it) } | ||
setBody(serializer.serialize(tags)) | ||
expectSuccess = true | ||
} | ||
return response.body<String>().toLong() | ||
} | ||
|
||
/** | ||
* Closes the given changeset. | ||
* | ||
* @param id id of the changeset to close | ||
* | ||
* @throws ConflictException if the changeset has already been closed or does not exist | ||
* @throws AuthorizationException if the application does not have permission to edit the map | ||
* (OAuth scope "write_api") | ||
* @throws ConnectionException if a temporary network connection problem occurs | ||
*/ | ||
suspend fun close(id: Long): Unit = wrapApiClientExceptions { | ||
try { | ||
httpClient.put(baseUrl + "changeset/$id/close") { | ||
userLoginSource.accessToken?.let { bearerAuth(it) } | ||
expectSuccess = true | ||
} | ||
} catch (e: ClientRequestException) { | ||
when (e.response.status) { | ||
HttpStatusCode.Conflict, HttpStatusCode.NotFound -> { | ||
throw ConflictException(e.message, e) | ||
} | ||
else -> throw e | ||
} | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
.../de/westnordost/streetcomplete/data/osm/edits/upload/changesets/ChangesetApiSerializer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package de.westnordost.streetcomplete.data.osm.edits.upload.changesets | ||
|
||
import de.westnordost.streetcomplete.util.ktx.attribute | ||
import de.westnordost.streetcomplete.util.ktx.endTag | ||
import de.westnordost.streetcomplete.util.ktx.startTag | ||
import nl.adaptivity.xmlutil.XmlWriter | ||
import nl.adaptivity.xmlutil.newWriter | ||
import nl.adaptivity.xmlutil.xmlStreaming | ||
|
||
class ChangesetApiSerializer { | ||
fun serialize(changesetTags: Map<String, String>): String { | ||
val buffer = StringBuilder() | ||
xmlStreaming.newWriter(buffer).serializeChangeset(changesetTags) | ||
return buffer.toString() | ||
} | ||
} | ||
|
||
private fun XmlWriter.serializeChangeset(changesetTags: Map<String, String>) { | ||
startTag("osm") | ||
startTag("changeset") | ||
for ((k, v) in changesetTags) { | ||
startTag("tag") | ||
attribute("k", k) | ||
attribute("v", v) | ||
endTag("tag") | ||
} | ||
endTag("changeset") | ||
endTag("osm") | ||
} |
Oops, something went wrong.