From 8f2876129af5f58e8d0c8f84ce206eb10c9e3882 Mon Sep 17 00:00:00 2001 From: Johanna Larsson Date: Fri, 31 Jul 2026 21:07:23 +0100 Subject: [PATCH] Add NSID module Very small and simple, last building block for at-uris --- lib/latch/nsid.ex | 40 ++++++++++++++++++++++++++++++++++++++++ test/latch/nsid_test.exs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 lib/latch/nsid.ex create mode 100644 test/latch/nsid_test.exs diff --git a/lib/latch/nsid.ex b/lib/latch/nsid.ex new file mode 100644 index 0000000..9ef3317 --- /dev/null +++ b/lib/latch/nsid.ex @@ -0,0 +1,40 @@ +defmodule Latch.NSID do + @moduledoc """ + Namespaced identifiers are used in atproto to reference lexicons, XRPC endpoints, and more. + + An example is: `site.standard.document`. + + https://atproto.com/specs/nsid + """ + + @syntax ~r/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$/ + + @doc """ + Validates the NSID against the specification. + """ + @spec valid?(String.t()) :: boolean() + def valid?(nsid) when is_binary(nsid) do + byte_size(nsid) <= 317 and String.match?(nsid, @syntax) + end + + @doc """ + Takes an NSID like `site.standard.document` and extracts the last part, `document`. + """ + @spec name(String.t()) :: String.t() + def name(nsid) do + nsid + |> String.split(".") + |> List.last() + end + + @doc """ + Takes an NSID like `site.standard.document` and extracts the authority part, `site.standard`. + """ + @spec authority(String.t()) :: String.t() + def authority(nsid) do + nsid + |> String.split(".") + |> Enum.drop(-1) + |> Enum.join(".") + end +end diff --git a/test/latch/nsid_test.exs b/test/latch/nsid_test.exs new file mode 100644 index 0000000..3da5568 --- /dev/null +++ b/test/latch/nsid_test.exs @@ -0,0 +1,32 @@ +defmodule Latch.NSIDTest do + use ExUnit.Case, async: true + + alias Latch.NSID + + describe "valid?/1" do + test "validates" do + assert NSID.valid?("com.example.fooBar") + assert NSID.valid?("net.users.bob.ping") + assert NSID.valid?("a-0.b-1.c") + assert NSID.valid?("a.b.c") + assert NSID.valid?("com.example.fooBarV2") + assert NSID.valid?("cn.8.lex.stuff") + + refute NSID.valid?("com.exa💩ple.thing") + refute NSID.valid?("com.example") + refute NSID.valid?("com.example.3") + end + end + + describe "name/1" do + test "grabs name" do + assert NSID.name("site.standard.document") == "document" + end + end + + describe "authority/1" do + test "grabs authority" do + assert NSID.authority("site.standard.document") == "site.standard" + end + end +end -- 2.51.2