Skip to content

Instantly share code, notes, and snippets.

@Vergil333
Created December 11, 2022 15:45
Show Gist options
  • Save Vergil333/58996d4e7900a4eb58521488e98c708f to your computer and use it in GitHub Desktop.
Save Vergil333/58996d4e7900a4eb58521488e98c708f to your computer and use it in GitHub Desktop.
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import java.net.HttpURLConnection
import java.net.IDN
import java.net.URI
import java.net.URL
import kotlin.reflect.KVisibility
import kotlin.reflect.full.findAnnotations
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaField
/**
* @param accessToken has to have [web, chatter_api, api] permissions for querying
* @param instanceUrl acquired in the same response as accessToken
*
* @credits to mjg123 https://github.com/mjg123/java-http-clients/blob/master/src/main/java/com/twilio/JavaHttpURLConnectionDemo.java
* @credits to madmax1028, broot https://discuss.kotlinlang.org/t/how-to-specify-generic-output-type-to-be-subclass-of-generic-type/24637/12
*/
class SalesforceClient(
@PublishedApi internal inline val accessToken: String,
@PublishedApi internal inline val instanceUrl: String,
) {
@PublishedApi
internal val apiVersion = "v53.0"
@PublishedApi
internal val mapper = jacksonObjectMapper().registerKotlinModule()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
inline fun <reified T: SObjectInterface> getAll(): SfResponse<T> =
getResponse<T>("SELECT ${getFields<T>().joinToString()} FROM ${T::class.simpleName}")
inline fun <reified T: SObjectInterface> count(): Int =
getResponse<T>("SELECT COUNT() FROM+${T::class.simpleName}")
.totalSize
@PublishedApi
internal inline fun <reified T: SObjectInterface> getResponse(query: String): SfResponse<T> {
val url = URL("$instanceUrl/services/data/$apiVersion/query?q=$query").toUri().toURL()
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.setRequestProperty("accept", "application/json")
connection.setRequestProperty("Authorization", "Bearer $accessToken")
return mapper.readValue(connection.inputStream, object : TypeReference<SfResponse<T>>() {})
}
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T: SObjectInterface> getFields(): List<String> {
val propertiesWeWant = T::class.java.kotlin.memberProperties
.filter { prop ->
prop.visibility == KVisibility.PUBLIC
&& listOf("java.util", "java.lang").contains(prop.javaField?.type?.packageName)
&& prop.name != "attributes"
}
val bodyProps = propertiesWeWant.mapNotNull { it.javaField?.getAnnotation(JsonProperty::class.java)?.value }
val constructorProps = T::class.java.kotlin.primaryConstructor?.parameters
?.filter { it.name in (propertiesWeWant.map { it.name }) }
?.map { it.findAnnotations(JsonProperty::class).first().value }
?: emptyList()
return bodyProps + constructorProps
}
/**
* URI is used for URL encoding.
*/
@PublishedApi
internal fun URL.toUri() = URI(
this.protocol,
this.userInfo,
IDN.toASCII(this.host),
this.port,
this.path,
this.query, this.ref,
)
}
class SfResponse<T: SObjectInterface>(
val totalSize: Int,
val done: Boolean,
val records: List<T>,
)
interface SObjectInterface
data class SObjectAttributes<T: SObjectType>(
val type: T,
val url: String? = null,
)
abstract class AbstractSObject<T: SObjectType>(
type: T,
) : SObjectInterface {
val attributes: SObjectAttributes<T> = SObjectAttributes(type = type)
@JsonProperty("Id") val id: String? = null
}
enum class SObjectType(val jsonName: String) {
@JsonProperty("TestObject__c") TEST_OBJECT__C("TestObject__c"),
}
/**
* @property createdById Lookup(User)
* @property createdDate Date/Time
* @property email__c Email
* @property id Lookup()
* @property isDeleted Checkbox
* @property lastModifiedById Lookup(User)
* @property lastModifiedDate Date/Time
* @property name Text(80)
* @property ownerId Lookup(User,Group)
* @property systemModstamp Date/Time
* @property userRecordAccessId Lookup(User Record Access)
*/
class TestObject__c (
@JsonProperty("CreatedById") val createdById: String,
@JsonProperty("CreatedDate") val createdDate: String,
@JsonProperty("email__c") val email__c: String? = null,
@JsonProperty("IsDeleted") val isDeleted: Boolean,
@JsonProperty("LastModifiedById") val lastModifiedById: String,
@JsonProperty("LastModifiedDate") val lastModifiedDate: String,
@JsonProperty("Name") val name: String? = null,
@JsonProperty("OwnerId") val ownerId: String,
@JsonProperty("SystemModstamp") val systemModstamp: String,
@JsonProperty("UserRecordAccessId") val userRecordAccessId: String? = null,
) : AbstractSObject<SObjectType>(type = SObjectType.TEST_OBJECT__C)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment