ruststream 0.7.5

Async messaging framework for Rust: broker-agnostic traits, router, codecs, and a conformance harness for broker authors.
Documentation
import React, {type ReactNode, useState} from 'react';
import Link from '@docusaurus/Link';
import Translate, {translate} from '@docusaurus/Translate';
import useBaseUrl from '@docusaurus/useBaseUrl';
import SectionHead from './SectionHead';
import {type NodeId, PART_PAGES} from './partPages';
import shared from './landing.module.css';
import styles from './architecture.module.css';

interface Part {
  id: NodeId;
  name: string;
  role: string;
}

// Called inside the component so the strings follow the page's language.
function parts(): Part[] {
  return [
    {
      id: 'broker',
      name: translate({id: 'landing.arch.broker', message: 'Broker'}),
      role: translate({
        id: 'landing.arch.broker.role',
        message:
          'A broker crate wraps one client and is held to the contract that the conformance suite checks; the core depends on no broker.',
      }),
    },
    {
      id: 'subscription',
      name: translate({id: 'landing.arch.subscription', message: 'Subscription'}),
      role: translate({
        id: 'landing.arch.subscription.role',
        message:
          'A subscription names what to consume and is resolved once, at startup, against the connected broker.',
      }),
    },
    {
      id: 'codec',
      name: translate({id: 'landing.arch.codec', message: 'Codec'}),
      role: translate({
        id: 'landing.arch.codec.role',
        message:
          'The codec turns the payload into the handler type: JSON, MessagePack or CBOR chosen at compile time, or raw bytes with no codec at all.',
      }),
    },
    {
      id: 'middleware',
      name: translate({id: 'landing.arch.middleware', message: 'Middleware'}),
      role: translate({
        id: 'landing.arch.middleware.role',
        message:
          'Consume and publish layers wrap every delivery: logging, metrics, tracing or your own.',
      }),
    },
    {
      id: 'handler',
      name: translate({id: 'landing.arch.handler', message: 'Handler'}),
      role: translate({
        id: 'landing.arch.handler.role',
        message:
          'An async function under #[subscriber]; what it returns settles the delivery or becomes the reply.',
      }),
    },
    {
      id: 'reply',
      name: translate({id: 'landing.arch.reply', message: 'Reply'}),
      role: translate({
        id: 'landing.arch.reply.role',
        message:
          'A reply type derives Outgoing and declares where it goes; the runtime encodes it and publishes it.',
      }),
    },
    {
      id: 'publisher',
      name: translate({id: 'landing.arch.publisher', message: 'Publisher'}),
      role: translate({
        id: 'landing.arch.publisher.role',
        message:
          'A publish policy paired with the connected broker; a handler also publishes through typed Out slots.',
      }),
    },
    {
      id: 'asyncapi',
      name: 'AsyncAPI',
      role: translate({
        id: 'landing.arch.asyncapi.role',
        message:
          'The same declarations generate an AsyncAPI 3.1 document, so the document cannot drift from the code.',
      }),
    },
    {
      id: 'testapp',
      name: 'TestApp',
      role: translate({
        id: 'landing.arch.testapp.role',
        message:
          'Tests start the production app with the broker in process and drive the real dispatch, with no server.',
      }),
    },
  ];
}

interface Geometry {
  x: number;
  y: number;
  w: number;
  h: number;
}

// The diagram on a 1000 x 420 canvas: the delivery runs left to right along the top row, the
// reply comes back right to left along the bottom one, and the side parts sit underneath.
const GEOMETRY: Record<NodeId, Geometry> = {
  broker: {x: 10, y: 40, w: 180, h: 220},
  subscription: {x: 270, y: 40, w: 180, h: 64},
  codec: {x: 530, y: 40, w: 180, h: 64},
  middleware: {x: 770, y: 8, w: 220, h: 284},
  handler: {x: 790, y: 40, w: 180, h: 220},
  publisher: {x: 270, y: 196, w: 180, h: 64},
  reply: {x: 530, y: 196, w: 180, h: 64},
  testapp: {x: 10, y: 336, w: 180, h: 64},
  asyncapi: {x: 400, y: 336, w: 180, h: 64},
};

const ARROWS = [
  'M190 72H262',
  'M450 72H522',
  'M710 72H782',
  'M790 228H718',
  'M530 228H458',
  'M270 228H198',
];

function Node({
  part,
  active,
  onActivate,
}: {
  part: Part;
  active: boolean;
  onActivate: (id: NodeId) => void;
}): ReactNode {
  const {x, y, w, h} = GEOMETRY[part.id];
  const frame = part.id === 'middleware';
  const href = useBaseUrl(PART_PAGES[part.id]);
  // An SVG link: Enter and a click open the part's page, and hover or focus shows its role.
  return (
    <a
      href={href}
      className={`${styles.node} ${frame ? styles.frame : ''} ${active ? styles.active : ''}`}
      aria-label={part.name}
      aria-describedby="landing-arch-role"
      onMouseEnter={() => onActivate(part.id)}
      onFocus={() => onActivate(part.id)}>
      <rect x={x} y={y} width={w} height={h} rx={frame ? 10 : 6} />
      <text
        x={frame ? x + 14 : x + w / 2}
        y={frame ? y + 20 : y + h / 2}
        textAnchor={frame ? 'start' : 'middle'}
        dominantBaseline="central">
        {part.name}
      </text>
    </a>
  );
}

/**
 * The path of a message through a service, as an SVG on wide screens and as a list on narrow
 * ones. Hovering or focusing a part shows its role, and each part links to its page.
 */
export default function Architecture(): ReactNode {
  const all = parts();
  const [active, setActive] = useState<NodeId>('handler');
  const current = all.find((part) => part.id === active)!;
  return (
    <section className={shared.section} aria-labelledby="landing-arch">
      <SectionHead
        id="landing-arch"
        eyebrow={<Translate id="landing.arch.eyebrow">Architecture</Translate>}
        title={<Translate id="landing.arch.title">The path of a message</Translate>}
        lede={
          <Translate id="landing.arch.lede">
            A delivery goes from the broker through the subscription and the codec to your handler,
            and the reply goes back the same way. Each part is a type the compiler checks.
          </Translate>
        }
      />
      <div className={styles.wide}>
        <svg
          className={styles.diagram}
          viewBox="0 0 1000 420"
          role="group"
          aria-label={translate({
            id: 'landing.arch.diagramLabel',
            message: 'Diagram of the parts a message passes through',
          })}>
          <defs>
            <marker
              id="landing-arch-arrow"
              viewBox="0 0 10 10"
              refX="9"
              refY="5"
              markerWidth="8"
              markerHeight="8"
              orient="auto-start-reverse">
              <path d="M0 0L10 5L0 10z" className={styles.arrowHead} />
            </marker>
          </defs>
          {ARROWS.map((d) => (
            <path key={d} d={d} className={styles.arrow} markerEnd="url(#landing-arch-arrow)" />
          ))}
          <path d="M100 336V268" className={styles.dotted} />
          <path d="M450 336L380 268M530 336L600 268" className={styles.dotted} />
          {/* The middleware frame precedes the handler, so the handler is drawn and hit on top. */}
          {all.map((part) => (
            <Node key={part.id} part={part} active={part.id === active} onActivate={setActive} />
          ))}
        </svg>
        <div id="landing-arch-role" className={styles.role} aria-live="polite">
          <span className={styles.roleName}>{current.name}</span>
          <span>{current.role}</span>
        </div>
      </div>
      <ol className={styles.narrow}>
        {all.map((part) => (
          <li key={part.id}>
            <Link className={styles.roleName} to={PART_PAGES[part.id]}>
              {part.name}
            </Link>
            <span>{part.role}</span>
          </li>
        ))}
      </ol>
    </section>
  );
}