Skip to content
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
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ let package = Package(
.copy("Resources/alien.jpg"),
.copy("Resources/loading.lottie"),
.copy("Resources/ArialBlack.ttf"),
.copy("Resources/unknown"),
.copy("Resources/unsupported.extension"),
]
),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ public struct DesignTokensConverter: Sendable {
/// - token: The `Token` that could not be processed.
/// - forKey: The key associated with the unsupported token.
case unsupportedToken(Token, forKey: String)
case malformedDynamicColorsGroup(TokenGroup, forKey: String)

/// A localized description of the error.
public var errorDescription: String? {
switch self {
case .invalidRootToken: "Invalid root token. Must be a group."
case let .unsupportedToken(_, key): "Unsupported token value type with key: \(key)."
case let .malformedDynamicColorsGroup(_, key): "Malformed dynamic colors group for key: \(key)."
}
}
}
Expand Down Expand Up @@ -146,7 +148,7 @@ public struct DesignTokensConverter: Sendable {
)
try extractor.extract(value, for: key, into: &caches)
case .group(let group):
try await extract(group, for: key, into: &caches)
try await extractDynamicColorsGroup(group, for: key, into: &caches)
case .alias, .array, .unknown:
throw Error.unsupportedToken(token, forKey: key)
}
Expand All @@ -161,16 +163,20 @@ public struct DesignTokensConverter: Sendable {
/// - key: The key associated with the token group.
/// - caches: The `SnappThemingDeclarationCaches` to store the dynamic color.
/// - Throws: `DesignTokensConverter.Error` if color conversion fails.
private func extract(
private func extractDynamicColorsGroup(
_ group: TokenGroup,
for key: String,
into caches: inout SnappThemingDeclarationCaches
) async throws {
guard
case .value(.color(let lightColorToken)) = group[configuration.dynamicColorKeys.light],
case .value(.color(let darkColorToken)) = group[configuration.dynamicColorKeys.dark]
case .value(.color(let lightColorToken)) = group[
configuration.dynamicColorKeys.light
],
case .value(.color(let darkColorToken)) = group[
configuration.dynamicColorKeys.dark
]
else {
return
throw Error.malformedDynamicColorsGroup(group, forKey: key)
}

let lightColorHEX = try lightColorToken.hex(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import SnappDesignTokens
import SnappTheming
import UniformTypeIdentifiers

extension DesignTokensTokenValueExtractor {
private enum ExtractionError: Error {
case invalidData
}
enum DesignTokensFileValueExtractorError: Error, Equatable {
case unknownFileType(URL)
case unsupportedFileType(URL, UTType)
}

extension DesignTokensTokenValueExtractor {
static var file: Self {
.init {
(
Expand All @@ -26,18 +27,14 @@ extension DesignTokensTokenValueExtractor {
options: .endLineWithLineFeed
)

guard
let base64EncodedData = Data(base64Encoded: base64EncodedString)
else {
throw ExtractionError.invalidData
}

guard
let contentType = UTType(
filenameExtension: fileValue.url.pathExtension
)
else {
return
throw DesignTokensFileValueExtractorError.unknownFileType(
fileValue.url
)
}

switch (
Expand Down Expand Up @@ -67,7 +64,7 @@ extension DesignTokensTokenValueExtractor {
source: SnappThemingDataURI(
type: contentType,
encoding: .base64,
data: base64EncodedData
data: data
)
)
)
Expand All @@ -76,11 +73,14 @@ extension DesignTokensTokenValueExtractor {
(_, _, .lotPathExtension):
caches.animationCache[key] = .value(
SnappThemingAnimationRepresentation(
animation: .lottie(base64EncodedData)
animation: .lottie(data)
)
)
case (_, _, _):
break
throw DesignTokensFileValueExtractorError.unsupportedFileType(
fileValue.url,
contentType
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import Foundation
import SnappDesignTokens
import SnappTheming

extension DesignTokensTokenValueExtractor {
private enum DesignTokensFontFamilyExtractionError: Error {
case fontsEmpty
}
enum DesignTokensFontFamilyValueExtractionError: Error {
case fontsEmpty
}

extension DesignTokensTokenValueExtractor {
static var fontFamily: Self {
.init(\.fontsCache) { (value: FontFamilyValue) in
/// As a [translation tool](https://tr.designtokens.org/format/#translation-tool) we
Expand All @@ -21,7 +21,7 @@ extension DesignTokensTokenValueExtractor {
/// we will just try to use the primary font and ignore fallbacks.
/// OS will just provided system font as fallback if required font is not available.
guard let fontName = value.names.first else {
throw DesignTokensFontFamilyExtractionError.fontsEmpty
throw DesignTokensFontFamilyValueExtractionError.fontsEmpty
}
return SnappThemingFontInformation(postScriptName: fontName, source: nil)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ import Foundation
import SnappDesignTokens
import SnappTheming

extension DesignTokensTokenValueExtractor {
private enum GradientExtractionError: Error {
case unresolvedReferences
}
enum DesignTokensGradientValueExtractionError: Error {
case unresolvedReferences
}

extension DesignTokensTokenValueExtractor {
static func gradient(using format: ColorHexFormat) -> Self {
.init(\.gradientsCache) { (value: GradientValue) in
let colorsWithPositions = try value.map { gradientColorValue in
guard
case .value(let color) = gradientColorValue.color,
case .value(let position) = gradientColorValue.position
else {
throw GradientExtractionError.unresolvedReferences
throw DesignTokensGradientValueExtractionError.unresolvedReferences
}
return (
color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import Foundation
import SnappDesignTokens
import SnappTheming

extension DesignTokensTokenValueExtractor {
private enum TypographyExtractionError: Error {
case unresolvedReferences
case unresolvedExpressions
case invalidFontSizeUnit
case fontsEmpty
}
enum DesignTokensTypographyValueExtractorError: Error, Equatable, Sendable {
case unresolvedReferences
case unresolvedExpressions
case invalidFontSizeUnit
case fontsEmpty
}

extension DesignTokensTokenValueExtractor {
static func typography(
fontWeightMapping: FontWeightMapping? = nil
) -> Self {
Expand All @@ -25,19 +25,22 @@ extension DesignTokensTokenValueExtractor {
case .value(let fontWeight) = value.fontWeight,
case .value(let fontSizeValue) = value.fontSize
else {
throw TypographyExtractionError.unresolvedReferences
throw DesignTokensTypographyValueExtractorError
.unresolvedReferences
}

guard let fontFamilyName = fontFamilyValue.names.first else {
throw TypographyExtractionError.fontsEmpty
throw DesignTokensTypographyValueExtractorError.fontsEmpty
}

guard case .constant(let fontSize) = fontSizeValue else {
throw TypographyExtractionError.unresolvedExpressions
throw DesignTokensTypographyValueExtractorError
.unresolvedExpressions
}

guard fontSize.unit == .px else {
throw TypographyExtractionError.invalidFontSizeUnit
throw DesignTokensTypographyValueExtractorError
.invalidFontSizeUnit
}

var fontName = fontFamilyName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ extension SnappThemingParser {
tokenProcessor processor: TokenProcessor = .defaultDesignTokensConversionProcessor(),
designTokensConverterConfiguration configuration: DesignTokensConverter.Configuration = .default
) async throws -> SnappThemingDeclaration {
guard let data = input.data(using: .utf8) else {
throw SnappThemingParserError.invalidData
}
// It is safe to force unwrap since Strings in Swift use Unicode internally.
let data = input.data(using: .utf8)!

let decoder = JSONDecoder()
let token: Token
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// DesignTokensTokenValueExtractorTests.swift
// SnappThemingDesignTokensSupport
//
// Created by Volodymyr Voiko on 18.12.2025.
//

import Testing
import SnappDesignTokens

@testable import SnappThemingDesignTokensSupport

struct DesignTokensTokenValueExtractorTests {
@Test
func testTypeMistmatchErrorHandling() async throws {
var caches = SnappThemingDeclarationCaches()
let extractor = DesignTokensTokenValueExtractor.color(using: .argb)
let tokenValue = TokenValue.number(0.5)
#expect(throws: DesignTokensTokenValueExtractorError.typeMistamatch) {
try extractor.extract(tokenValue, for: "key", into: &caches)
}
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading