Agregando Openverse: el tercer proveedor de imágenes, y el primero que nadie tiene que configurar
Retomé un issue que llevaba un tiempo bloqueado en el backlog. La idea es simple: agregar Openverse (un proyecto de WordPress, antes CC Search) como tercera fuente de imágenes destacadas junto a Pexels y Unsplash, dando acceso a más de 800 millones de obras Creative Commons y de dominio público. Estaba bloqueado por dos cosas que tenían que existir primero: imágenes destacadas alojadas en el propio servidor (porque Openverse simplemente apunta a donde sea que viva la imagen original, y esos enlaces se rompen con el tiempo) y atribución en los posts publicados (porque las licencias CC en efecto exigen acreditar al creador). Ambas se lanzaron hace semanas, así que esto por fin quedó desbloqueado.
Antes de escribir código, probé la API real de Openverse directamente en vez de confiar solo en la descripción del issue. Eso rindió frutos de inmediato: confirmé que la búsqueda anónima funciona de verdad sin ninguna credencial, obtuve los nombres exactos de los campos (url, thumbnail, creator_url, license, license_version, todos confirmados contra una respuesta real para una foto de Flickr), y confirmé la forma del endpoint de token OAuth2 con un par de solicitudes deliberadamente inválidas (un 400 con errores de campo requerido en /v1/auth_tokens/register/, un 401 invalid_client en /v1/auth_tokens/token/) sin llegar a registrar ninguna app ni tocar ninguna cuenta real.
El cliente sigue de cerca el estilo interno ya existente de Pexels/Unsplash: el mismo patrón de solicitudes con Net::HTTP, la misma costura de inyección de dependencias connection: para las pruebas, las mismas clases anidadas Error/AuthError. La única desviación deliberada es que las credenciales vacías no lanzan un error, el nivel anónimo de Openverse es una forma de primera clase y con soporte oficial de usar la API, no un respaldo degradado, así que Openverse::Client.new sin nada pasado simplemente funciona. Esa única decisión de diseño terminó teniendo más implicaciones posteriores de las que esperaba (más sobre eso abajo).
La otra decisión de diseño real fue sobre el manejo de licencias. Tanto Pexels como Unsplash usan una sola licencia general para cada resultado (una foto de Pexels siempre es “Pexels License”, punto), así que sus clientes simplemente devuelven una constante. Openverse agrupa obras bajo todo tipo de variantes CC, BY, BY-SA, CC0, Public Domain Mark, así que la licencia había que leerla por resultado. Me equivoqué en el primer intento: construí el nombre para mostrar anteponiendo siempre “CC” al código que llegara, lo que produce “CC CC0” para imágenes de dominio público. Mi propia prueba lo detectó en el sentido de que escribí la prueba para verificar ese valor exactamente incorrecto, y luego la revisión automatizada detectó que la prueba estaba verificando un error, no una función. Se corrigió con un mapa explícito para las licencias sin atribución (cc0 → “CC0”, pdm → “Public Domain Mark”) y la ruta con prefijo CC solo para las variantes que sí requieren atribución.
El cacheo de tokens tenía un error más sutil. Rails.cache.fetch no puede fijar un TTL derivado de lo que el propio bloque devuelve, así que armé a mano una lectura seguida de escritura en su lugar. Mi primera versión ponía como piso del TTL calculado el margen de seguridad en vez de cero, así que un token con un expires_in corto (digamos 30 segundos, con un margen de 60 segundos) igual quedaba cacheado por 60 segundos, lo que significaba que la app seguiría usando un token ya muerto y recibiría un 401 silencioso durante la segunda mitad de esa ventana. La revisión también detectó que el camino de manejo de errores al obtener el token se había desviado del manejo de errores del camino de búsqueda, un 500 del endpoint de token se estaba reportando incorrectamente como un AuthError, la misma clase que un client secret genuinamente incorrecto, lo cual mandaría a cualquiera por el camino de depuración equivocado.
Lo que más me sorprendió, sin embargo, no fue un error, fue una decisión de diseño que no había pensado del todo hasta que el ángulo de altura de la revisión la nombró directamente: como Openverse funciona de forma anónima, es el primer proveedor del registro que está “configurado” incondicionalmente. Pexels y Unsplash solo aparecen en los resultados de búsqueda si un administrador pegó una clave; Openverse siempre aparece. Eso significa que una instalación que nunca configuró ningún proveedor de imágenes ahora hace una llamada HTTPS saliente real a api.openverse.org en cada búsqueda de imagen destacada, cuando antes no hacía nada. Decidí no arreglar esto dentro del mismo PR, es exactamente lo que pedía el issue (“anónimo por defecto”), y construir una forma de optar por no usar un proveedor sin clave es una función más grande de lo que este issue abarcaba. Pero es un cambio de comportamiento real que valía la pena nombrar explícitamente en vez de descubrirlo por accidente más adelante.
Qué sigue: nada está directamente bloqueado por esto. El caso extremo de credenciales a medio llenar (client_id fijado sin un secret, cayendo en silencio al modo anónimo sin ninguna indicación) y la exposición de la vista de asignación de imágenes en lote al límite de tasa de Openverse bajo scroll intenso quedaron documentados en el PR como riesgos aceptados de baja severidad, en vez de abrirse como issues nuevos, ninguno de los dos causa actualmente un comportamiento incorrecto, solo una experiencia degradada en un caso extremo.
Lecturas relacionadas
El error de la imagen hero que en realidad eran tres errores
Una tarjeta que mentía diciendo que un post no tenía imagen hero, una ruta relativa resuelta contra el origen equivocado, y un 403 de hotlinking basado en Referer de mi propio blog contra mi propia app.
Agregar Unsplash como segundo proveedor de imágenes hero
Replicando el cliente de Pexels línea por línea, un ping de seguimiento de descargas fire-and-forget, y una URL controlada por el navegador que casi terminó con la clave de la API pegada a ella.
Construir imágenes de portada autoalojadas antes de tener algo que las necesitara
Un pipeline de descarga y realojamiento para imágenes con licencia CC: vips desde un buffer, una conversión forzada a UTF-8 que habría corrompido los bytes del JPEG, y una cola desactualizada que hizo parecer un error a una protección que funcionaba bien.