Skip to content

Commit

Permalink
Image service URL construction (#221)
Browse files Browse the repository at this point in the history
* wip

* bump to 1.1.18

* split tests

* address feedback

* wip

* bump to 1.1.18

* split tests

* address feedback

* Create kotlin extensions

* Bump version

* add test

---------

Co-authored-by: micbakos-rdx <[email protected]>
  • Loading branch information
danvleju-rdx and micbakos-rdx authored Sep 26, 2024
1 parent b6c2379 commit 0d4c725
Show file tree
Hide file tree
Showing 16 changed files with 569 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions apple/Sources/Sargon/Util/URL+image.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation
import SargonUniFFI

extension URL {
public func isVectorImage(type: VectorImageType) -> Bool {
imageUrlUtilsIsVectorImage(url: self.absoluteString, imageType: type)
}

public func imageURL(imageServiceURL: URL, size: CGSize) throws -> URL {
try imageUrlUtilsMakeImageUrl(
url: self.absoluteString,
imageServiceUrl: imageServiceURL.absoluteString,
width: UInt32(size.width),
height: UInt32(size.height)
)
}
}
37 changes: 37 additions & 0 deletions apple/Tests/TestCases/Prelude/ImageURLTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@testable import Sargon

import CustomDump
import Foundation
import Sargon
import SargonUniFFI
import XCTest

final class ImageURLTests: XCTestCase {
func test_is_vector_image() {
let svgURL = URL(string: "https://svgshare.com/i/U7z.svg")!

XCTAssert(svgURL.isVectorImage(type: .svg))
}

func test_image_url() throws {
let size = CGSize(width: 1024, height: 1024)
let imageServiceURL = URL(string: "https://image-service-dev.extratools.works")!
let svgURL = URL(string: "https://svgshare.com/i/U7z.svg")!

XCTAssertEqual(
try svgURL.imageURL(imageServiceURL: imageServiceURL, size: size),
URL(string: "https://image-service-dev.extratools.works/?imageOrigin=https%3A%2F%2Fsvgshare.com%2Fi%2FU7z.svg&imageSize=1024x1024&format=png")
)
}

func test_image_url_with_data_url() throws {
let size = CGSize(width: 1024, height: 1024)
let imageServiceURL = URL(string: "https://image-service-dev.extratools.works")!
let svgDataURL = URL(string: "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%201000%201000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpolygon%20fill%3D%22hsla%2890%2C99%25%2C52%25%2C1%29%22%20points%3D%220%2C%200%2C%201000%2C%201000%2C%200%2C%201000%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpolygon%20fill%3D%22hsla%28199%2C90%25%2C64%25%2C1%29%22%20points%3D%221000%2C%201000%2C%201000%2C%200%2C%200%2C%200%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpath%20d%3D%22M1000%2C229%20A1000%2C1000%2C0%2C0%2C0%2C229%2C1000%20L1000%2C1000%20z%22%20fill%3D%22hsla%28140%2C98%25%2C61%25%2C1%29%22%2F%3E%0A%3Cpath%20d%3D%22M392%2C500%20L608%2C500%20M500%2C392%20L500%2C608%22%20stroke%3D%22hsla%2847%2C92%25%2C61%25%2C1%29%22%20stroke-width%3D%2272%22%2F%3E%0A%3C%2Fsvg%3E")!

XCTAssertEqual(
try svgDataURL.imageURL(imageServiceURL: imageServiceURL, size: size),
URL(string: "https://image-service-dev.extratools.works/?imageOrigin=data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520viewBox%253D%25220%25200%25201000%25201000%2522%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%250A%253Cpolygon%2520fill%253D%2522hsla%252890%252C99%2525%252C52%2525%252C1%2529%2522%2520points%253D%25220%252C%25200%252C%25201000%252C%25201000%252C%25200%252C%25201000%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpolygon%2520fill%253D%2522hsla%2528199%252C90%2525%252C64%2525%252C1%2529%2522%2520points%253D%25221000%252C%25201000%252C%25201000%252C%25200%252C%25200%252C%25200%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M1000%252C229%2520A1000%252C1000%252C0%252C0%252C0%252C229%252C1000%2520L1000%252C1000%2520z%2522%2520fill%253D%2522hsla%2528140%252C98%2525%252C61%2525%252C1%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M392%252C500%2520L608%252C500%2520M500%252C392%2520L500%252C608%2522%2520stroke%253D%2522hsla%252847%252C92%2525%252C61%2525%252C1%2529%2522%2520stroke-width%253D%252272%2522%252F%253E%250A%253C%252Fsvg%253E&imageSize=1024x1024&format=png")
)
}
}
2 changes: 1 addition & 1 deletion crates/sargon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sargon"
version = "1.1.16"
version = "1.1.19"
edition = "2021"
build = "build.rs"

Expand Down
120 changes: 120 additions & 0 deletions crates/sargon/src/core/utils/image_url_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::prelude::*;
use crate::types::*;

pub fn is_vector_image(url: &str, image_type: VectorImageType) -> bool {
let parsed_url = match parse_url(url) {
Ok(parsed) => parsed,
Err(_) => return false,
};
let query_parameters = parsed_url
.query_pairs()
.into_owned()
.collect::<HashMap<String, String>>();
let image_url_string = query_parameters
.get("imageOrigin")
.map(|s| s.as_str())
.unwrap_or(url);

image_url_string
.starts_with(&format!("data:image/{}", image_type.data_url_type()))
|| image_url_string
.to_lowercase()
.ends_with(image_type.url_extension())
}

pub fn make_image_url(
url: &str,
image_service_url: &str,
width: u32,
height: u32,
) -> Result<Url> {
const MIN_SIZE: u32 = 64;

let image_origin = url_encode(url);
let image_size =
format!("{}x{}", width.max(MIN_SIZE), height.max(MIN_SIZE));
let mut query =
format!("imageOrigin={}&imageSize={}", image_origin, image_size);

if is_vector_image(url, VectorImageType::Svg) {
query.push_str("&format=png");
}

let mut parsed_image_service_url = parse_url(image_service_url)?;
parsed_image_service_url.set_query(Some(&query));

Ok(parsed_image_service_url)
}

#[cfg(test)]
mod tests {

use super::*;

#[test]
fn test_is_vector_image() {
let svg_url = "https://svgshare.com/i/U7z.svg";
let pdf_url = "https://example.com/image.pdf";
let svg_data_url = "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%201000%201000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpolygon%20fill%3D%22hsla%2890%2C99%25%2C52%25%2C1%29%22%20points%3D%220%2C%200%2C%201000%2C%201000%2C%200%2C%201000%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpolygon%20fill%3D%22hsla%28199%2C90%25%2C64%25%2C1%29%22%20points%3D%221000%2C%201000%2C%201000%2C%200%2C%200%2C%200%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpath%20d%3D%22M1000%2C229%20A1000%2C1000%2C0%2C0%2C0%2C229%2C1000%20L1000%2C1000%20z%22%20fill%3D%22hsla%28140%2C98%25%2C61%25%2C1%29%22%2F%3E%0A%3Cpath%20d%3D%22M392%2C500%20L608%2C500%20M500%2C392%20L500%2C608%22%20stroke%3D%22hsla%2847%2C92%25%2C61%25%2C1%29%22%20stroke-width%3D%2272%22%2F%3E%0A%3C%2Fsvg%3E";
let pdf_data_url = "data:image/pdf,dummydata";

assert!(is_vector_image(svg_url, VectorImageType::Svg));
assert!(is_vector_image(pdf_url, VectorImageType::Pdf));
assert!(is_vector_image(svg_data_url, VectorImageType::Svg));
assert!(is_vector_image(pdf_data_url, VectorImageType::Pdf));
}

#[test]
fn test_is_vector_image_invalid_url() {
let url = "invalid";

assert_eq!(is_vector_image(url, VectorImageType::sample()), false);
}

#[test]
fn test_is_vector_image_with_image_origin_url() {
let url = "https://image-service-dev.extratools.works/?imageOrigin=https%3A%2F%2Fsvgshare.com%2Fi%2FU7z.svg&imageSize=1024x1024&format=png";

assert!(is_vector_image(url, VectorImageType::Svg));
}

#[test]
fn test_is_vector_image_with_image_origin_data_url() {
let data_url = "https://image-service-dev.extratools.works/?imageOrigin=data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520viewBox%253D%25220%25200%25201000%25201000%2522%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%250A%253Cpolygon%2520fill%253D%2522hsla%252890%252C99%2525%252C52%2525%252C1%2529%2522%2520points%253D%25220%252C%25200%252C%25201000%252C%25201000%252C%25200%252C%25201000%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpolygon%2520fill%253D%2522hsla%2528199%252C90%2525%252C64%2525%252C1%2529%2522%2520points%253D%25221000%252C%25201000%252C%25201000%252C%25200%252C%25200%252C%25200%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M1000%252C229%2520A1000%252C1000%252C0%252C0%252C0%252C229%252C1000%2520L1000%252C1000%2520z%2522%2520fill%253D%2522hsla%2528140%252C98%2525%252C61%2525%252C1%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M392%252C500%2520L608%252C500%2520M500%252C392%2520L500%252C608%2522%2520stroke%253D%2522hsla%252847%252C92%2525%252C61%2525%252C1%2529%2522%2520stroke-width%253D%252272%2522%252F%253E%250A%253C%252Fsvg%253E&imageSize=1024x1024&format=png";

assert!(is_vector_image(data_url, VectorImageType::Svg));
}

#[test]
fn test_make_image_url_svg_url() {
let image_service_url = "https://image-service-dev.extratools.works/";
let image_origin_url = "https://svgshare.com/i/U7z.svg";

pretty_assertions::assert_eq!(
make_image_url(image_origin_url, image_service_url, 1024, 1024).unwrap().to_string(),
"https://image-service-dev.extratools.works/?imageOrigin=https%3A%2F%2Fsvgshare.com%2Fi%2FU7z.svg&imageSize=1024x1024&format=png".to_string()
);
}

#[test]
fn test_make_image_url_svg_data_url() {
let image_service_url = "https://image-service-dev.extratools.works/";
let image_origin_data_url = "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%201000%201000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpolygon%20fill%3D%22hsla%2890%2C99%25%2C52%25%2C1%29%22%20points%3D%220%2C%200%2C%201000%2C%201000%2C%200%2C%201000%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpolygon%20fill%3D%22hsla%28199%2C90%25%2C64%25%2C1%29%22%20points%3D%221000%2C%201000%2C%201000%2C%200%2C%200%2C%200%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpath%20d%3D%22M1000%2C229%20A1000%2C1000%2C0%2C0%2C0%2C229%2C1000%20L1000%2C1000%20z%22%20fill%3D%22hsla%28140%2C98%25%2C61%25%2C1%29%22%2F%3E%0A%3Cpath%20d%3D%22M392%2C500%20L608%2C500%20M500%2C392%20L500%2C608%22%20stroke%3D%22hsla%2847%2C92%25%2C61%25%2C1%29%22%20stroke-width%3D%2272%22%2F%3E%0A%3C%2Fsvg%3E";

pretty_assertions::assert_eq!(
make_image_url(image_origin_data_url, image_service_url, 1024, 1024).unwrap().to_string(),
"https://image-service-dev.extratools.works/?imageOrigin=data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520viewBox%253D%25220%25200%25201000%25201000%2522%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%250A%253Cpolygon%2520fill%253D%2522hsla%252890%252C99%2525%252C52%2525%252C1%2529%2522%2520points%253D%25220%252C%25200%252C%25201000%252C%25201000%252C%25200%252C%25201000%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpolygon%2520fill%253D%2522hsla%2528199%252C90%2525%252C64%2525%252C1%2529%2522%2520points%253D%25221000%252C%25201000%252C%25201000%252C%25200%252C%25200%252C%25200%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M1000%252C229%2520A1000%252C1000%252C0%252C0%252C0%252C229%252C1000%2520L1000%252C1000%2520z%2522%2520fill%253D%2522hsla%2528140%252C98%2525%252C61%2525%252C1%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M392%252C500%2520L608%252C500%2520M500%252C392%2520L500%252C608%2522%2520stroke%253D%2522hsla%252847%252C92%2525%252C61%2525%252C1%2529%2522%2520stroke-width%253D%252272%2522%252F%253E%250A%253C%252Fsvg%253E&imageSize=1024x1024&format=png".to_string()
);
}

#[test]
fn test_make_image_url_not_svg() {
let image_service_url = "https://image-service-dev.extratools.works/";
let image_origin_url = "https://sgo4bmuvgu4t24bvdcfbndmnxigspezdsnzoevon2jb5odru7auq.arweave.net/kZ3AspU1OT1wNRiKFo2Nug0nkyOTcuJVzdJD1w40-Ck";

pretty_assertions::assert_eq!(
make_image_url(image_origin_url, image_service_url, 1024, 1024).unwrap().to_string(),
"https://image-service-dev.extratools.works/?imageOrigin=https%3A%2F%2Fsgo4bmuvgu4t24bvdcfbndmnxigspezdsnzoevon2jb5odru7auq.arweave.net%2FkZ3AspU1OT1wNRiKFo2Nug0nkyOTcuJVzdJD1w40-Ck&imageSize=1024x1024".to_string()
);
}
}
55 changes: 55 additions & 0 deletions crates/sargon/src/core/utils/image_url_utils_uniffi_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::prelude::*;
use crate::types::*;

#[uniffi::export]
pub fn image_url_utils_is_vector_image(
url: &str,
image_type: VectorImageType,
) -> bool {
is_vector_image(url, image_type)
}

#[uniffi::export]
pub fn image_url_utils_make_image_url(
url: &str,
image_service_url: &str,
width: u32,
height: u32,
) -> Result<Url> {
make_image_url(url, image_service_url, width, height)
}

#[cfg(test)]
mod tests {

use super::*;

#[test]
fn test_image_url_utils_is_vector_image() {
let url = "https://svgshare.com/i/U7z.svg";
let image_type = VectorImageType::Svg;

assert_eq!(
is_vector_image(url, image_type),
image_url_utils_is_vector_image(&url, image_type)
)
}

#[test]
fn test_image_url_utils_make_image_url() {
let url = "https://svgshare.com/i/U7z.svg";
let image_service_url = "https://image-service-dev.extratools.works";
let width = 1024;
let height = 1024;

assert_eq!(
make_image_url(url, image_service_url, width, height),
image_url_utils_make_image_url(
url,
image_service_url,
width,
height
)
)
}
}
4 changes: 4 additions & 0 deletions crates/sargon/src/core/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
mod factory;
mod image_url_utils;
mod image_url_utils_uniffi_fn;
mod logged_panic;
mod serialization;
mod string_utils;

pub use factory::*;
pub use image_url_utils::*;
pub use image_url_utils_uniffi_fn::*;
pub use logged_panic::*;
pub use serialization::*;
pub use string_utils::*;
Expand Down
20 changes: 20 additions & 0 deletions crates/sargon/src/core/utils/string_utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::CommonError;
use url::form_urlencoded;
use url::Url;

/// Returns the last `n` chars of the &str `s`. If `s` is shorter than `n`
Expand Down Expand Up @@ -42,6 +43,10 @@ pub fn parse_url(s: impl AsRef<str>) -> Result<Url, CommonError> {
})
}

pub fn url_encode(s: impl AsRef<str>) -> String {
form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -104,4 +109,19 @@ mod tests {
fn test_parse_url_invalid() {
assert!(parse_url("https/radixdlt").is_err());
}

#[test]
fn test_url_encode() {
let url = "https://svgshare.com/i/U7z.svg";
let data_url = "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%201000%201000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpolygon%20fill%3D%22hsla%2890%2C99%25%2C52%25%2C1%29%22%20points%3D%220%2C%200%2C%201000%2C%201000%2C%200%2C%201000%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpolygon%20fill%3D%22hsla%28199%2C90%25%2C64%25%2C1%29%22%20points%3D%221000%2C%201000%2C%201000%2C%200%2C%200%2C%200%22%20transform%3D%22scale%28-1%2C1%29%20translate%28-1000%29%22%2F%3E%0A%3Cpath%20d%3D%22M1000%2C229%20A1000%2C1000%2C0%2C0%2C0%2C229%2C1000%20L1000%2C1000%20z%22%20fill%3D%22hsla%28140%2C98%25%2C61%25%2C1%29%22%2F%3E%0A%3Cpath%20d%3D%22M392%2C500%20L608%2C500%20M500%2C392%20L500%2C608%22%20stroke%3D%22hsla%2847%2C92%25%2C61%25%2C1%29%22%20stroke-width%3D%2272%22%2F%3E%0A%3C%2Fsvg%3E";

pretty_assertions::assert_eq!(
url_encode(url),
"https%3A%2F%2Fsvgshare.com%2Fi%2FU7z.svg"
);
pretty_assertions::assert_eq!(
url_encode(data_url),
"data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520viewBox%253D%25220%25200%25201000%25201000%2522%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%250A%253Cpolygon%2520fill%253D%2522hsla%252890%252C99%2525%252C52%2525%252C1%2529%2522%2520points%253D%25220%252C%25200%252C%25201000%252C%25201000%252C%25200%252C%25201000%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpolygon%2520fill%253D%2522hsla%2528199%252C90%2525%252C64%2525%252C1%2529%2522%2520points%253D%25221000%252C%25201000%252C%25201000%252C%25200%252C%25200%252C%25200%2522%2520transform%253D%2522scale%2528-1%252C1%2529%2520translate%2528-1000%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M1000%252C229%2520A1000%252C1000%252C0%252C0%252C0%252C229%252C1000%2520L1000%252C1000%2520z%2522%2520fill%253D%2522hsla%2528140%252C98%2525%252C61%2525%252C1%2529%2522%252F%253E%250A%253Cpath%2520d%253D%2522M392%252C500%2520L608%252C500%2520M500%252C392%2520L500%252C608%2522%2520stroke%253D%2522hsla%252847%252C92%2525%252C61%2525%252C1%2529%2522%2520stroke-width%253D%252272%2522%252F%253E%250A%253C%252Fsvg%253E"
);
}
}
4 changes: 4 additions & 0 deletions crates/sargon/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mod ffi_url;
mod vector_image_type;
mod vector_image_type_uniffi_fn;

pub use ffi_url::*;
pub use vector_image_type::*;
pub use vector_image_type_uniffi_fn::*;
64 changes: 64 additions & 0 deletions crates/sargon/src/types/vector_image_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::prelude::*;

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, uniffi::Enum)]
pub enum VectorImageType {
Svg,
Pdf,
}

impl VectorImageType {
pub fn url_extension(&self) -> &str {
match self {
VectorImageType::Svg => ".svg",
VectorImageType::Pdf => ".pdf",
}
}

pub fn data_url_type(&self) -> &str {
match self {
VectorImageType::Svg => "svg+xml",
VectorImageType::Pdf => "pdf",
}
}
}

impl HasSampleValues for VectorImageType {
fn sample() -> Self {
Self::Svg
}

fn sample_other() -> Self {
Self::Pdf
}
}

#[cfg(test)]
mod tests {
use super::*;

#[allow(clippy::upper_case_acronyms)]
type SUT = VectorImageType;

#[test]
fn inequality() {
assert_ne!(SUT::sample(), SUT::sample_other());
}

#[test]
fn equality() {
assert_eq!(SUT::sample(), SUT::sample());
assert_eq!(SUT::sample_other(), SUT::sample_other());
}

#[test]
fn test_url_extension() {
assert_eq!(SUT::sample().url_extension(), ".svg");
assert_eq!(SUT::sample_other().url_extension(), ".pdf");
}

#[test]
fn test_data_url_type() {
assert_eq!(SUT::sample().data_url_type(), "svg+xml");
assert_eq!(SUT::sample_other().data_url_type(), "pdf");
}
}
Loading

0 comments on commit 0d4c725

Please sign in to comment.