UUID iOS : tester le format, la version et l’ordre des octets
Pour une même commande, l’API renvoie un UUID en majuscules, le cache local le conserve en minuscules, mais la logique de déduplication compare directement les chaînes. Ailleurs, un UUID est stocké sous forme binaire et, à la relecture, l’ordre des octets des premiers groupes a changé. Ces problèmes peuvent être détectés lors des vérifications préalables au build sur un Mac cloud, sans lancer de simulateur. Commencez par fixer le contrat d’entrée, de comparaison d’identité et de sérialisation à l’aide de vecteurs Swift fixes, puis faites appliquer les mêmes règles au projet iOS.
Distinguer le format des règles métier
Un UUID est un identifiant de 128 bits. Sa représentation textuelle courante se compose de cinq groupes de caractères hexadécimaux, de longueurs respectives 8, 4, 4, 4 et 12. Les quatre traits d’union occupent des positions fixes, mais cette apparence ne garantit pas qu’il s’agisse d’un UUID aléatoire de version 4.
Définissez d’abord trois règles : les majuscules sont-elles acceptées, les espaces en début et en fin sont-ils autorisés, et faut-il imposer une version ? Dans cet article, les majuscules et les minuscules sont acceptées, mais les espaces, les accolades, les formes sans traits d’union et les caractères non ASCII sont refusés. La version 4 et la variante correspondante ne sont vérifiées que pour les champs qui exigent explicitement un identifiant aléatoire.
« Pouvoir analyser une valeur » relève des capacités de la bibliothèque ; « autoriser son entrée dans le système » relève du protocole. Ne faites pas de la tolérance du parseur le contrat implicite de votre API.
Le cas de l’UUID entièrement composé de zéros doit lui aussi faire l’objet d’une décision explicite. Il peut respecter le format textuel général sans pour autant devoir remplacer automatiquement une valeur « non renseignée ». Pour les champs facultatifs, privilégiez une sémantique de valeur nulle explicite.
Exécuter des vecteurs fixes sur un Mac cloud
Préparez un Mac cloud sur lequel la chaîne d’outils de développement souhaitée est sélectionnée, puis enregistrez le contenu suivant dans uuid-contract.swift. Le script utilise uniquement Foundation et ne dépend ni d’un projet, ni du réseau, ni de nombres aléatoires. Ici, precondition sert à interrompre le script de test en cas d’échec ; il ne doit pas servir à traiter les entrées externes dans l’application.
import Foundation
func parseUUID(_ text: String, requireV4: Bool = false) -> UUID? {
let bytes = Array(text.utf8)
guard bytes.count == 36 else { return nil }
let separators: Set<Int> = [8, 13, 18, 23]
for (index, byte) in bytes.enumerated() {
if separators.contains(index) {
guard byte == 45 else { return nil }
} else {
let isHex = (48...57).contains(byte)
|| (65...70).contains(byte)
|| (97...102).contains(byte)
guard isHex else { return nil }
}
}
guard let value = UUID(uuidString: text) else { return nil }
if requireV4 {
let raw = value.uuid
guard raw.6 >> 4 == 4, raw.8 & 0xC0 == 0x80 else {
return nil
}
}
return value
}
let valid = "00112233-4455-4677-8899-aabbccddeeff"
let parsed = parseUUID(valid, requireV4: true)!
precondition(parsed.uuidString.lowercased() == valid)
precondition(parseUUID(valid.uppercased()) == parsed)
let invalid = [
" " + valid,
valid + "\n",
"{" + valid + "}",
valid.replacingOccurrences(of: "-", with: ""),
valid.replacingOccurrences(of: "a", with: "a")
]
for input in invalid {
precondition(parseUUID(input) == nil)
}
let otherVersion = "00112233-4455-5677-8899-aabbccddeeff"
precondition(parseUUID(otherVersion) != nil)
precondition(parseUUID(otherVersion, requireV4: true) == nil)
let wrongVariant = "00112233-4455-4677-0899-aabbccddeeff"
precondition(parseUUID(wrongVariant) != nil)
precondition(parseUUID(wrongVariant, requireV4: true) == nil)
let raw = parsed.uuid
let wire: [UInt8] = [
raw.0, raw.1, raw.2, raw.3,
raw.4, raw.5, raw.6, raw.7,
raw.8, raw.9, raw.10, raw.11,
raw.12, raw.13, raw.14, raw.15
]
precondition(wire == [
0x00, 0x11, 0x22, 0x33,
0x44, 0x55, 0x46, 0x77,
0x88, 0x99, 0xaa, 0xbb,
0xcc, 0xdd, 0xee, 0xff
])
Exécutez xcrun swift uuid-contract.swift. En cas de succès, le script ne produit aucune sortie ; en cas d’échec, il renvoie un code de sortie non nul. Le pipeline doit préserver ce code de sortie. Consignez également la sortie de xcrun swift --version pour pouvoir relancer les tests et comparer les résultats après une mise à jour de la chaîne d’outils.
Centraliser la comparaison d’identité
Dans l’application, comparez autant que possible des valeurs UUID plutôt que les chaînes brutes reçues de l’API. Si vous devez écrire une clé textuelle en base de données, appelez uuidString.lowercased() après une analyse stricte réussie afin d’obtenir une représentation canonique unique, en minuscules et avec traits d’union.
Ne supprimez pas les traits d’union et les espaces en début ou en fin avant de tenter l’analyse. Ces « corrections » masquent les erreurs du protocole en amont et peuvent ramener à une même clé deux entrées qui auraient dû être rejetées. Si l’interface utilisateur autorise le collage, elle peut proposer une étape de nettoyage explicite, mais la couche API ne doit pas reprendre ce comportement silencieusement.
Avant de migrer des données existantes, recensez les collisions après normalisation de la casse. Si deux enregistrements ne diffèrent que par la casse des lettres de leur UUID, il ne faut pas écraser directement l’un d’eux. Vérifiez d’abord les liens métier, puis fusionnez les enregistrements ou isolez les anomalies, en conservant une trace du résultat de la migration.
Vérifier l’ordre de tous les octets lors des échanges binaires
Le vecteur ci-dessus énumère les 16 octets dans l’ordre des caractères hexadécimaux du texte de l’UUID. Cet ordre doit faire partie du protocole d’échange, et non dépendre de la manière dont la machine courante stocke les entiers.
Ne lisez pas les huit premiers octets comme un entier natif pour les réécrire directement. De même, ne supposez pas qu’un identifiant possède la même disposition mémoire que celle de ce protocole au seul motif qu’une API l’appelle GUID. Lors d’un échange entre langages, exigez que les deux côtés produisent exactement la même séquence d’octets hexadécimaux à partir d’un même texte fixe.
Utiliser aussi un échantillon indépendant pour la relecture
Un simple test « j’encode, puis je décode moi-même » ne suffit pas : si les deux opérations commettent la même erreur, l’aller-retour réussira malgré tout. Le récepteur doit utiliser directement un échantillon fixe de 16 octets et vérifier par assertion que le texte reconstitué est identique au vecteur d’origine. Testez ensuite le rejet obligatoire des entrées de 15 et de 17 octets, sans ajout de zéros ni troncature.
Intégrer les vérifications au véritable chemin d’analyse
Le script valide un exemple d’application des règles. Pour les mettre en œuvre, déplacez la fonction d’analyse dans un module partagé, appelé aussi bien par le décodage des réponses de l’API que par les lectures et écritures du cache et les outils d’importation. Placez les vecteurs correspondants dans la cible de tests pour éviter que le script de vérification réussisse alors que l’application utilise encore un autre chemin, plus permissif.
Couvrez quatre groupes de cas : les formes valides en majuscules et en minuscules doivent correspondre à la même identité ; les caractères non autorisés et les espaces doivent être refusés ; la restriction de version doit s’appliquer uniquement aux champs désignés ; les échantillons binaires doivent correspondre octet par octet. Dans chaque groupe, vérifiez les valeurs renvoyées, sans vous contenter d’affirmer que le code « ne plante pas ».
Dans les journaux d’échec, consignez uniquement le nom du champ, la catégorie d’erreur et le numéro de l’échantillon, sans exposer de véritables identifiants métier. Relancez d’abord cette suite de tests de contrat lors d’une mise à jour de la chaîne d’outils, du remplacement de la bibliothèque de sérialisation côté serveur ou d’un import de données historiques. Situer l’échec au niveau du texte, des règles d’acceptation ou de l’ordre des octets est plus direct que de multiplier les hypothèses entre le cache et l’API.
Questions fréquentes
Faut-il imposer la version 4 à tous les UUID ?
Non. Cette règle ne convient qu’aux interfaces qui exigent explicitement des identifiants aléatoires de version 4. Le format et les versions autorisées sont deux contrôles distincts.
La casse change-t-elle l’identité d’un UUID ?
Non. Après validation, utilisez une représentation unique en minuscules avec tirets comme clé. Conservez la saisie d’origine uniquement pour le diagnostic.
Réservez un Mac dédié à votre prochain build.
Choisissez un Mac cloud non virtualisé et conservez votre environnement Xcode et vos workflows CI. Configurez la machine selon la charge de vos projets ; sa disponibilité réelle est indiquée en temps réel dans la console.