Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SKUs SDK Changes to Support Receipt Submission #13820

Merged
merged 5 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions components/skus/browser/rs/cxx/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ use std::error::Error;

use crate::ffi;

impl From<skus::errors::InternalError> for ffi::SkusResult {
fn from(e: skus::errors::InternalError) -> Self {
ffi::SkusResult::from(&e)
}
}

impl From<&skus::errors::InternalError> for ffi::SkusResult {
fn from(e: &skus::errors::InternalError) -> Self {
match e {
Expand Down Expand Up @@ -57,15 +63,30 @@ impl From<ffi::SkusResult> for skus::errors::InternalError {
fn from(e: ffi::SkusResult) -> Self {
match e {
ffi::SkusResult::RequestFailed => skus::errors::InternalError::RequestFailed,
ffi::SkusResult::InternalServer => skus::errors::InternalError::InternalServer(
skus::http::http::status::StatusCode::from_u16(599).expect("100 < 599 < 1000"),
),
ffi::SkusResult::BadRequest => skus::errors::InternalError::BadRequest(
skus::http::http::StatusCode::from_u16(499).expect("100 < 499 < 1000"),
),
ffi::SkusResult::UnhandledStatus => skus::errors::InternalError::UnhandledStatus(
skus::http::http::StatusCode::from_u16(699).expect("100 < 699 < 1000"),
),
ffi::SkusResult::InternalServer => {
skus::errors::InternalError::InternalServer(skus::models::APIError {
code: 599,
message: "internal server error".to_string(),
error_code: "unknown".to_string(),
data: serde_json::Value::Null,
})
}
ffi::SkusResult::BadRequest => {
skus::errors::InternalError::BadRequest(skus::models::APIError {
code: 499,
message: "bad request error".to_string(),
error_code: "unknown".to_string(),
data: serde_json::Value::Null,
})
}
ffi::SkusResult::UnhandledStatus => {
skus::errors::InternalError::UnhandledStatus(skus::models::APIError {
code: 699,
message: "unhandled error".to_string(),
error_code: "unknown".to_string(),
data: serde_json::Value::Null,
})
}
ffi::SkusResult::RetryLater => skus::errors::InternalError::RetryLater(None),
ffi::SkusResult::NotFound => skus::errors::InternalError::NotFound,
ffi::SkusResult::SerializationFailed => {
Expand Down
96 changes: 65 additions & 31 deletions components/skus/browser/rs/cxx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ mod ffi {
callback_state: UniquePtr<CredentialSummaryCallbackState>,
domain: String,
);
fn submit_receipt(
self: &CppSDK,
callback: SubmitReceiptCallback,
callback_state: UniquePtr<SubmitReceiptCallbackState>,
order_id: String,
receipt: String,
);
}

unsafe extern "C++" {
Expand Down Expand Up @@ -184,6 +191,8 @@ mod ffi {
type PrepareCredentialsPresentationCallback = crate::PrepareCredentialsPresentationCallback;
type CredentialSummaryCallbackState;
type CredentialSummaryCallback = crate::CredentialSummaryCallback;
type SubmitReceiptCallbackState;
type SubmitReceiptCallback = crate::SubmitReceiptCallback;
}
}

Expand All @@ -201,9 +210,7 @@ fn initialize_sdk(ctx: UniquePtr<ffi::SkusContext>, env: String) -> Box<CppSDK>
Err(_) => println!("tracing_subscriber - maybe already initialized"),
};

let env = env
.parse::<skus::Environment>()
.unwrap_or(skus::Environment::Local);
let env = env.parse::<skus::Environment>().unwrap_or(skus::Environment::Local);

let pool = LocalPool::new();
let spawner = pool.spawner();
Expand All @@ -212,10 +219,7 @@ fn initialize_sdk(ctx: UniquePtr<ffi::SkusContext>, env: String) -> Box<CppSDK>
is_shutdown: Rc::new(RefCell::new(false)),
pool: Rc::new(RefCell::new(pool)),
spawner: spawner.clone(),
ctx: Rc::new(RefCell::new(NativeClientContext {
environment: env.clone(),
ctx,
})),
ctx: Rc::new(RefCell::new(NativeClientContext { environment: env.clone(), ctx })),
},
env,
None,
Expand Down Expand Up @@ -251,12 +255,7 @@ impl CppSDK {
) {
let spawner = self.sdk.client.spawner.clone();
if spawner
.spawn_local(refresh_order_task(
self.sdk.clone(),
callback,
callback_state,
order_id,
))
.spawn_local(refresh_order_task(self.sdk.clone(), callback, callback_state, order_id))
.is_err()
{
debug!("pool is shutdown");
Expand Down Expand Up @@ -332,6 +331,30 @@ impl CppSDK {

self.sdk.client.try_run_until_stalled();
}

fn submit_receipt(
self: &CppSDK,
callback: SubmitReceiptCallback,
callback_state: UniquePtr<ffi::SubmitReceiptCallbackState>,
order_id: String,
receipt: String,
) {
let spawner = self.sdk.client.spawner.clone();
if spawner
.spawn_local(submit_receipt_task(
self.sdk.clone(),
callback,
callback_state,
order_id,
receipt,
))
.is_err()
{
debug!("pool is shutdown");
}

self.sdk.client.try_run_until_stalled();
}
}

#[repr(transparent)]
Expand Down Expand Up @@ -384,11 +407,7 @@ async fn fetch_order_credentials_task(
callback_state: UniquePtr<ffi::FetchOrderCredentialsCallbackState>,
order_id: String,
) {
match sdk
.fetch_order_credentials(&order_id)
.await
.map_err(|e| e.into())
{
match sdk.fetch_order_credentials(&order_id).await.map_err(|e| e.into()) {
Ok(_) => callback.0(callback_state.into_raw(), ffi::SkusResult::Ok),
Err(e) => callback.0(callback_state.into_raw(), e),
}
Expand All @@ -415,16 +434,10 @@ async fn prepare_credentials_presentation_task(
domain: String,
path: String,
) {
match sdk
.prepare_credentials_presentation(&domain, &path)
.await
.map_err(|e| e.into())
{
Ok(Some(presentation)) => callback.0(
callback_state.into_raw(),
ffi::SkusResult::Ok,
&presentation,
),
match sdk.prepare_credentials_presentation(&domain, &path).await.map_err(|e| e.into()) {
Ok(Some(presentation)) => {
callback.0(callback_state.into_raw(), ffi::SkusResult::Ok, &presentation)
}
Ok(None) => callback.0(callback_state.into_raw(), ffi::SkusResult::Ok, ""),
Err(e) => callback.0(callback_state.into_raw(), e, ""),
}
Expand Down Expand Up @@ -454,9 +467,7 @@ async fn credential_summary_task(
.matching_credential_summary(&domain)
.await
.and_then(|summary| {
summary
.map(|summary| serde_json::to_string(&summary).map_err(|e| e.into()))
.transpose()
summary.map(|summary| serde_json::to_string(&summary).map_err(|e| e.into())).transpose()
})
.map_err(|e| e.into())
{
Expand All @@ -465,3 +476,26 @@ async fn credential_summary_task(
Err(e) => callback.0(callback_state.into_raw(), e, ""),
}
}

#[repr(transparent)]
pub struct SubmitReceiptCallback(
pub extern "C" fn(callback_state: *mut ffi::SubmitReceiptCallbackState, result: ffi::SkusResult),
);

unsafe impl ExternType for SubmitReceiptCallback {
type Id = type_id!("skus::SubmitReceiptCallback");
type Kind = cxx::kind::Trivial;
}

async fn submit_receipt_task(
sdk: Rc<skus::sdk::SDK<NativeClient>>,
callback: SubmitReceiptCallback,
callback_state: UniquePtr<ffi::SubmitReceiptCallbackState>,
order_id: String,
receipt: String,
) {
match sdk.submit_receipt(&order_id, &receipt).await.map_err(|e| e.into()) {
Ok(_) => callback.0(callback_state.into_raw(), ffi::SkusResult::Ok),
Err(e) => callback.0(callback_state.into_raw(), e),
}
}
10 changes: 9 additions & 1 deletion components/skus/browser/rs/cxx/src/shim.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ class RefreshOrderCallbackState {
base::OnceCallback<void(const std::string&)> cb;
};

class SubmitReceiptCallbackState {
public:
SubmitReceiptCallbackState();
~SubmitReceiptCallbackState();
base::OnceCallback<void(const std::string&)> cb;
};

class SkusUrlLoader {
public:
virtual ~SkusUrlLoader() = default;
Expand All @@ -76,7 +83,6 @@ class SkusContext {
using RefreshOrderCallback = void (*)(RefreshOrderCallbackState* callback_state,
SkusResult result,
rust::cxxbridge1::Str order);

using FetchOrderCredentialsCallback =
void (*)(FetchOrderCredentialsCallbackState* callback_state,
SkusResult result);
Expand All @@ -88,6 +94,8 @@ using CredentialSummaryCallback =
void (*)(CredentialSummaryCallbackState* callback_state,
SkusResult result,
rust::cxxbridge1::Str summary);
using SubmitReceiptCallback =
void (*)(SubmitReceiptCallbackState* callback_state, SkusResult result);

void shim_logMessage(rust::cxxbridge1::Str file,
uint32_t line,
Expand Down
49 changes: 34 additions & 15 deletions components/skus/browser/rs/lib/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use core::fmt;
use core::fmt::Display;

use http::StatusCode;
use std::error::Error;
use std::time::Duration;

use crate::models::APIError;

use serde_json::Value;

/// Internal errors. Most application-level developers will likely not
/// need to pay any attention to these.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InternalError {
RequestFailed,
InternalServer(http::StatusCode),
BadRequest(http::StatusCode),
UnhandledStatus(http::StatusCode),
InternalServer(APIError),
BadRequest(APIError),
UnhandledStatus(APIError),
RetryLater(Option<Duration>),
NotFound,
SerializationFailed,
Expand All @@ -36,28 +41,36 @@ impl Display for InternalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InternalError::RequestFailed => write!(f, "HTTP request failed"),
InternalError::InternalServer(status) => {
InternalError::InternalServer(app_err) => {
let code: StatusCode =
StatusCode::from_u16(app_err.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
write!(
f,
"Server internal error: {} {}",
status.as_str(),
status.canonical_reason().unwrap_or("unknown")
code.as_str(),
code.canonical_reason().unwrap_or("unknown")
)
}
InternalError::BadRequest(status) => {
InternalError::BadRequest(app_err) => {
let code: StatusCode =
StatusCode::from_u16(app_err.code).unwrap_or(StatusCode::BAD_REQUEST);
write!(
f,
"Bad client request: {} {}",
status.as_str(),
status.canonical_reason().unwrap_or("unknown")
"Bad client request: {} {} - {} {}",
code.as_str(),
code.canonical_reason().unwrap_or("unknown"),
app_err.error_code,
app_err.data.get("validationErrors").unwrap_or(&Value::Null)
)
}
InternalError::UnhandledStatus(status) => {
InternalError::UnhandledStatus(app_err) => {
let code: StatusCode =
StatusCode::from_u16(app_err.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
write!(
f,
"Unhandled request status: {} {}",
status.as_str(),
status.canonical_reason().unwrap_or("unknown")
code.as_str(),
code.canonical_reason().unwrap_or("unknown")
)
}
InternalError::RetryLater(after) => write!(
Expand Down Expand Up @@ -108,13 +121,19 @@ impl Display for InternalError {

impl Error for InternalError {}

impl From<(InternalError, usize)> for InternalError {
fn from((e, _attempt): (InternalError, usize)) -> Self {
e
}
}

impl From<serde_json::Error> for InternalError {
fn from(_: serde_json::Error) -> Self {
InternalError::SerializationFailed
}
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SkusError(pub(crate) InternalError);

impl Display for SkusError {
Expand Down
22 changes: 16 additions & 6 deletions components/skus/browser/rs/lib/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,32 @@ pub use http;
use http::{Request, Response};

use crate::errors::*;
use crate::models::APIError;
use crate::sdk::SDK;

static BASE_DELAY_MS: u64 = 1000;
static MAX_DELAY_MS: u64 = 10000;

/// Default mapping of server response codes to be used after explicitly handling known response codes
impl<T> From<http::Response<T>> for InternalError {
fn from(resp: http::Response<T>) -> Self {
impl From<http::Response<Vec<u8>>> for InternalError {
fn from(resp: http::Response<Vec<u8>>) -> Self {
event!(Level::DEBUG, "coming from response to internal error");
let body = resp.body();

let app_err: APIError = serde_json::from_slice(&body).unwrap_or(APIError {
code: 0,
message: "unknown".to_string(),
error_code: "".to_string(),
data: Value::Null,
});

match resp.status() {
http::StatusCode::TOO_MANY_REQUESTS => {
InternalError::RetryLater(delay_from_response(&resp))
}
status if status.is_client_error() => InternalError::BadRequest(status),
status if status.is_server_error() => InternalError::InternalServer(status),
status => InternalError::UnhandledStatus(status),
status if status.is_client_error() => InternalError::BadRequest(app_err),
status if status.is_server_error() => InternalError::InternalServer(app_err),
_ => InternalError::UnhandledStatus(app_err),
}
}
}
Expand Down Expand Up @@ -172,7 +183,6 @@ impl<U> SDK<U> {
resp.body = %to_string_pretty(&v).unwrap(),
"recieved response",
);

Ok(Response::from_parts(parts, body))
}
}
Expand Down
Loading