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}