001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common.io.resource; 019 020import com.google.common.collect.ImmutableSet; 021import com.google.common.io.ByteSink; 022import com.google.common.io.ByteSource; 023import com.google.common.io.CharSource; 024import com.google.common.net.MediaType; 025 026import org.jspecify.annotations.Nullable; 027 028import java.net.URI; 029import java.util.Set; 030 031/// TeapotResource 🫖 is an <a href="https://www.rfc-editor.org/rfc/rfc2324.html">RFC 2324</a> 032/// inspired resource for <code>coffee:/</code> etc. URLs. It returns "I'm a teapot" (as UTF-8 033/// encoded text; but NOT as <code>message/coffeepot</code> media type!). It ignores anything 034/// written to it. 035/// 036/// All international (§3.) coffee URI schemes are fully supported; and e.g. 037/// `kafo://demo.enola.dev/pot-7?#syrup-type=Vanilla` is valid for sameideanoj. Please note 038/// the following (major and breaking, sorry) backwards incompatibility: Due to what is assumed to 039/// be a typo in the original RFC, for _Catalan, French and Galician_ the correctly accented URI 040/// scheme `café` ("caf%C3%A9") instead of `cafè` ("caf%C3%E8") is used. However, we 041/// do also support `cafè` as the _Italian_ URI scheme; this should help to avoid major 042/// systems interoperability disasters. (It may still cause minor language issues; where full 043/// interop with the original RFC spec is required, we recommend using a Locale override URL 044/// parameter; e.g. `cafè://demo.enola.dev/pot-7?hl=fr`.) 045/// 046/// Planned future features: 047/// 048/// - Support multilingual (Locale) responses 049/// - Teapot MCP A2A AI ML Agent (coming up!) 050/// - For audio media type requests, return <a 051/// href="https://en.wikipedia.org/wiki/I%27m_a_Little_Teapot">I'm a Little Teapot</a> 052/// - For image media type requests, return a <a 053/// href="https://en.wikipedia.org/wiki/Utah_teapot">Utah (Newell) teapot</a> 054/// - <a href="https://www.rfc-editor.org/rfc/rfc7168.html">RFC 7168</a> support 055/// 056/// 057/// @see <a 058/// href="https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol">Wikipedia</a> 059/// @see <a href="https://save418.com/">save418.com</a> 060public class TeapotResource extends BaseResource implements Resource { 061 062 public static final Set<String> SCHEMES = 063 ImmutableSet.of( 064 "koffie", "qæhvæ", "قهوة", "akeita", "koffee", "kahva", "kafe", "café", "咖啡", 065 "kava", "káva", "kaffe", "coffee", "kafo", "kohv", "kahvi", "Kaffee", "καφέ", 066 "कौफ़ी", "コーヒー", "커피", "кофе", "กาแฟ"); 067 068 private static final String CONTENT = "I'm a teapot"; 069 070 public static class Provider implements ResourceProvider { 071 @Override 072 public @Nullable Resource getResource(java.net.URI uri) { 073 if (SCHEMES.contains(uri.getScheme())) { 074 return new TeapotResource(uri); 075 } 076 return null; 077 } 078 } 079 080 public TeapotResource(URI uri) { 081 super(uri, MediaType.PLAIN_TEXT_UTF_8); 082 } 083 084 @Override 085 public ByteSource byteSource() { 086 return charSource().asByteSource(mediaType().charset().get()); 087 } 088 089 @Override 090 public CharSource charSource() { 091 return CharSource.wrap(CONTENT); 092 } 093 094 @Override 095 public ByteSink byteSink() { 096 return NullByteSink.INSTANCE; 097 } 098}