#lang racket/base ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Canonical concept identity helpers. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (require racket/string uuid) (provide concept-id? normalize-concept-id new-concept-id normalized-or-new-concept-id) (define prefixed-uuid-concept-id-pattern #px"(?i:^concept-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$)") ;; Concept ids are stored as plain UUID strings. Validation accepts uppercase ;; input, while normalization always produces the canonical lowercase form. (define (concept-id? value) (uuid-string? value)) (define (normalize-concept-id value) (cond [(uuid-string? value) (string-downcase value)] [(and (string? value) (regexp-match prefixed-uuid-concept-id-pattern value)) => (lambda (match) (string-downcase (cadr match)))] [else #f])) (define (new-concept-id) (uuid-string)) (define (normalized-or-new-concept-id value) (or (normalize-concept-id value) (new-concept-id))) (module+ test (require rackunit) (define sample "a3c4f0d1-22e5-4c42-9a40-cc864993f785") (check-true (concept-id? sample)) (check-true (concept-id? (string-upcase sample))) (check-false (concept-id? (string-append "concept-" sample))) (check-equal? (normalize-concept-id (string-upcase sample)) sample) (check-equal? (normalize-concept-id (string-append "concept-" sample)) sample) (check-false (normalize-concept-id "legacy:a:concept-1")) (check-true (concept-id? (new-concept-id))))