How do I create a ContentType for application/schema+json with akka http? - akka-http

In akka-http there is
//ContentType.scala
val `application/json` = ContentType(MediaTypes.`application/json`)
In the same vein, how do I create a ContentType for
application/schema+json
application/schema-instance+json
?
Reference:
https://json-schema.org/draft/2019-09/json-schema-core.html
https://github.com/akka/akka-http/blob/v10.2.3/akka-http-core/src/main/scala/akka/http/scaladsl/model/ContentType.scala#L119
https://github.com/akka/akka-http/blob/v10.2.3/akka-http-core/src/main/scala/akka/http/scaladsl/model/MediaType.scala#L357-L358
//MediaType.scala
val `application/json` = awfc("json", HttpCharsets.`UTF-8`, "json")
val `application/json-patch+json` = awfc("json-patch+json", HttpCharsets.`UTF-8`)

Per https://doc.akka.io/docs/akka-http/current/common/http-model.html#registering-custom-media-types
Maybe something like:
object SchemaContentTypes {
val `application/schema+json` = ContentType(SchemaMediaTypes.`application/schema+json`)
val `application/schema-instance+json` = ContentType(SchemaMediaTypes.`application/schema-instance+json`)
}
object SchemaMediaTypes {
val `application/schema+json` =
MediaType.customWithFixedCharset("application", "schema+json", HttpCharsets.`UTF-8`, "schema.json")
val `application/schema-instance+json` =
MediaType.customWithFixedCharset("application", "schema-instance+json", HttpCharsets.`UTF-8`)
}

Related

How to test a service function in kotlin which uses a late init Id?

I am new to testing in kotlin and I was wondering how I can test this function:
this is my SegmentService file:
fun createSegmentFromUserIds(userIds: List<String>, name:String, appId: String): Segmentation {
val filter = createUserIdFilter(userIds)
val createSegmentRequest = CreateSegmentRequest(
name = name, attribute = filter, type = SegmentType.STATIC
)
val segmentation = segmentMetaService.saveSegmentInfo(createSegmentRequest, appId)
querySegmentService.runUpdateQuery(users = userIds, appId = appId, segmentId = segmentation.id)
return segmentation
}
this is the saveSegmentInfo function:
fun saveSegmentInfo(createSegmentFilter: CreateSegmentRequest, appId: String): Segmentation {
val segmentInfo = segmentationRepository.save(createSegmentFilter.let {
Segmentation(
attribute = it.attribute, name = it.name, appId = appId, type = it.type
)
})
logger.info("Segment info saved with id: ${segmentInfo.id}, name: ${segmentInfo.name}")
return segmentInfo
}
and this is the Segmentation Document
#Document(Segmentation.COLLECTION_NAME)
class Segmentation(
#Field(ATTRIBUTE)
val attribute: Filter,
#Field(NAME)
val name: String,
#Indexed
#Field(APP_ID)
val appId: String,
#Field(STATUS)
var status: SegmentStatus = SegmentStatus.CREATED,
#Field(TYPE)
var type: SegmentType = SegmentType.STATIC,
#Field(USER_COUNT)
var userCount: Long? = null,
) {
#Id
lateinit var id: String
#Field(CREATION_DATE)
var creationDate: Date = Date()
}
I have written this test for it:
class SegmentServiceTest {
private val segmentMetaService = mock(SegmentMetaService::class.java)
private val querySegmentService = mock(QuerySegmentService::class.java)
private val converterService = mock(ConverterService::class.java)
private val userAttributeService = mock(UserAttributeService::class.java)
private val segmentConsumerUserInfoProducer = mock(SegmentConsumerUsersInfoProducer::class.java)
private val segmentationRepository = mock(SegmentationRepository::class.java)
#Test
fun `createSegmentFromUserIds should create a new segment`() {
val segmentService = SegmentService(
segmentMetaService = segmentMetaService,
querySegmentService = querySegmentService,
converterService = converterService,
userAttributeService = userAttributeService,
segmentConsumerUserInfoProducer = segmentConsumerUserInfoProducer
)
val userIds = listOf("user-1", "user-2", "user-3")
val filter = AndFilter(
operations = listOf(
AndFilter(
operations = listOf(
StringListContainsFilter(
field = "userId", operationType = StringQueryOperationType.IN, values = userIds
)
)
)
)
)
val createSegmentRequest = CreateSegmentRequest(
name = "segment-name", attribute = filter, type = SegmentType.STATIC
)
val segment = Segmentation(attribute = filter, name = "segment-name", type = SegmentType.STATIC, appId = "app-id" )
`when`(segmentationRepository.save(any())).thenReturn(segment)
`when`(segmentMetaService.saveSegmentInfo(createSegmentRequest, "app-id")).thenReturn(segment)
val createdSegment = segmentService.createSegmentFromUserIds(userIds = userIds, name = "segment-name", appId = "app-id")
assertEquals(segment, createdSegment)
}
}
but I am facing this error:
kotlin.UninitializedPropertyAccessException: lateinit property id has not been initialized
So what am I doing wrong here?
How can I initialize the Id? or should I refactor my code so for it to become testable?
I think you are mocking / providing the answers to the wrong calls.
you mock segmentationRepository.save(..) but this call won't ever be made since that call is inside segmentMetaService.saveSegmentInfo(...) which is mocked so the real code is not called.
when you call segmentService.createSegmentFromUserIds(..), my guess (stack trace would be useful on the error, BTW), is that this method proceeds passed the invocation of segmentMetaService.saveSegmentInfo(...) but in the next line you call querySegmentService.runUpdateQuery(users = userIds, appId = appId, segmentId = segmentation.id) which is accessing the uninitialised id.
The fix will be to set the segment.id when you set up the object for the answer to segmentMetaService.saveSegmentInfo(..)

when calling Coinbase pro sandbox api, invalid: 401 Unauthorized. Text: "{"message":"invalid signature"}" Kotlin

What I wanted to do is call the Coinbase sandbox API to get all accounts for a profile.
I am following the official documentation https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_getaccounts
But keep getting this error
Client request(https://api-public.sandbox.exchange.coinbase.com/accounts) invalid: 401 Unauthorized. Text: "{"message":"invalid signature"}"
Am I missing something?
suspend fun getTradingAccounts(): String {
val timestamp = getTimeStamp()
val response: String = client.get("https://api-public.sandbox.exchange.coinbase.com/accounts") {
headers {
append("Accept", "application/json")
append("Content-Type", "application/json")
append(
"cb-access-key",
"MY_KEY..."
)
append("cb-access-passphrase", "MY_PASSPHRASE....")
append("cb-access-sign", signMessage(
timestamp = timestamp,
method = "GET",
path = "https://api-public.sandbox.exchange.coinbase.com/accounts"
))
append("cb-access-timestamp", timestamp)
}
}
return response
}
private fun getTimeStamp(): String {
val time = LocalDateTime.now()
val zoneId = ZoneId.of("Europe/London")
val epoch = time.atZone(zoneId).toEpochSecond()
return epoch.toString()
}
#Throws(NoSuchAlgorithmException::class, InvalidKeyException::class)
private fun signMessage(timestamp: String, method: String, path: String): String {
val prehash = timestamp + method + path
val sha256_HMAC = Mac.getInstance("HmacSHA256")
val secretDecoded: ByteArray = Base64.getDecoder().decode("MY_API_KEY==")
val secret_key = SecretKeySpec(secretDecoded, "HmacSHA256")
sha256_HMAC.init(secret_key)
return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(prehash.toByteArray()))
}
As I see your code for signing messages doesn't use request body JSON string when concatenating components for a message. Here is my version for the function that returns the same string as in NodeJS script for the same inputs:
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
fun main() {
val result = sign(
secret = "TVlfQVBJX0tFWQ==",
timestampMs = System.currentTimeMillis(),
method = "POST",
requestPath = "/orders",
request = Req(
price = "1.0",
size = "1.0",
side = "buy",
product_id = "BTC-USD",
)
)
println(result)
}
#OptIn(ExperimentalSerializationApi::class)
fun sign(timestampMs: Long, method: String, requestPath: String, request: Req, secret: String): String {
val accessTimestamp = timestampMs / 1000.0
val message = "%.3f".format(accessTimestamp) + method + requestPath + Json.encodeToString(request)
val sha256HMAC = Mac.getInstance("HmacSHA256")
val key: ByteArray = Base64.getDecoder().decode(secret)
sha256HMAC.init(SecretKeySpec(key, "HmacSHA256"))
return Base64.getEncoder().encodeToString(
sha256HMAC.doFinal(message.toByteArray())
)
}
#Serializable
data class Req(val price: String, val size: String, val side: String, val product_id: String)

Kotlin Map Serialization and Deserialization error class with map objects

I'm trying to create serialize and deserialize an object. I'm doing on this way:
var mapper = ObjectMapper()
var stringTest = mapper.writeValueAsString(stringContainsObjectFilter);
val raw: Any = mapper.readValue(stringTest, Any::class.java)
val filtersFromString = mapper.convertValue(raw, ObjectFilter::class.java)
val objectFilters = ObjectFilter()
quoteFilters.rosterLine= filtersFromString.rosterLine
quoteFilters.outOfPocketMax = filtersFromString.outOfPocketMax
quoteFilters.cost = filtersFromString.cost
This is the class that I want to serialize and deserialize
open class ObjectFilter{
var rosterLine: Map<RosterEnum, List<Int>>? = null
var outOfPocketMax: Map<String?, List<Int>>? = null
var monthlyCost: List<MonthlyCost>? = null
}
But I'm getting the following message:
"Response[ObjectFilters]; could not unconvert attribute"
Can anyone help me please? I've been trying to solve it without result :(
I found the solution, this is what I did and it worked:
val rawResult = mapper.convertValue(mapper.readValue(string, Any::class.java), Any::class.java)
val filtersFromString = rawResult as? ObjectFilter ?: ObjectFilter()

Kotlin: Unresolved reference: use,

I am trying to integrate a Dialogflow Agent with Pepper: https://developer.softbankrobotics.com/pepper-qisdk/lessons/integrating-chatbot-dialogflow
I followed all the steps until the Testing your agent in standalone section, where I have to add the following Kotlin code to the DialogflowSource class:
import com.google.auth.oauth2.ServiceAccountCredentials
import com.google.cloud.dialogflow.v2.*
import java.io.InputStream
class DialogflowDataSource constructor(credentialsStream : InputStream) {
private val credentials : ServiceAccountCredentials
= ServiceAccountCredentials.fromStream(credentialsStream)
fun detectIntentTexts(
text: String,
sessionId: String,
languageCode: String
): String? {
val sessionsSettings = SessionsSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
.build()
SessionsClient.create(sessionsSettings).use { sessionsClient -> //Error: Unresolved reference for .use
val session = SessionName.of(credentials.projectId, sessionId)
val textInput = TextInput.newBuilder()
.setText(text).setLanguageCode(languageCode)
val queryInput = QueryInput
.newBuilder().setText(textInput).build()
val response = sessionsClient.detectIntent(session, queryInput)
return response.queryResult.fulfillmentText
}
} //Error: A 'return' expression required in a function with a block body ('{...}')
}
I'm new to Kotlin, so I don't really know how to fix this. Any help would be appreciated!
First, why would you use use? It seems you meant to call apply instead. But in fact you could just write:
fun detectIntentTexts(
text: String,
sessionId: String,
languageCode: String
): String? {
val sessionsSettings = SessionsSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
.build()
val sessionClient = SessionsClient.create(sessionsSettings)
val session = SessionName.of(credentials.projectId, sessionId)
val textInput =
TextInput.newBuilder().setText(text).setLanguageCode(languageCode)
val queryInput = QueryInput.newBuilder().setText(textInput).build()
val response = sessionsClient.detectIntent(session, queryInput)
return response.queryResult.fulfillmentText
}
But if you care using use (or apply), the lambda you provide to it should not directly make the outer function detectIntentTexts return. Instead, let your lambda return its result locally, and let detectIntentTexts return it:
fun detectIntentTexts(
text: String,
sessionId: String,
languageCode: String
): String? {
val sessionsSettings = SessionsSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
.build()
return SessionsClient.create(sessionsSettings).apply { sessionsClient ->
val session = SessionName.of(credentials.projectId, sessionId)
val textInput = TextInput.newBuilder()
.setText(text).setLanguageCode(languageCode)
val queryInput = QueryInput
.newBuilder().setText(textInput).build()
val response = sessionsClient.detectIntent(session, queryInput)
response.queryResult.fulfillmentText
}
}
}

How to remove ZoneId?

Thank you always for your help.
I got a problem in my project.
I want to delete Zoned Id and leave only time.
How to remove zoned Id?
It is written using kotlin in Spring Boot Framework.
data class AvailableScheduleRequest (
val address: String,
val date: LocalDate,
val people: Long,
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "Asia/Seoul")
val hour: ZonedDateTime
)
#RestController
class Controller(
val newBookingElasticSearchService: NewBookingElasticSearchService
) {
#RequestMapping("/")
#ResponseBody
fun get(#RequestBody param: Map<String, Any>): List<AvailableRestaurant> {
val json = JacksonUtils.om.writeValueAsString(param)
val availableScheduleRequest =
JacksonUtils.om.readValue(json, AvailableScheduleRequest::class.java)
println(availableScheduleRequest.hour)
}
}
object JacksonUtils {
val om = ObjectMapper()
init {
val module = SimpleModule()
module.addDeserializer(String::class.java, JsonRawValueDeserializer.INSTANCE)
om.registerModule(JavaTimeModule())
om.registerModule(KotlinModule())
om.registerModule(module)
om.dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
om.setTimeZone(TimeZone.getDefault())
om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
om.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true)
om.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)
om.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
}
This code outputs:
2019-10-02T07:00+09:00[Asia/Seoul]
But I wanna get this:
2019-10-02T07:00+09:00
How to get this?
Thank you for your efforts all the time!!
Instead of ZonedDateTime, you could use OffsetDateTime.