Skip to content

lqdev/WebmentionFs

Repository files navigation

WebmentionFs

NuGet Version Build Status License: MIT

A comprehensive F# library for processing webmentions according to the W3C specification. WebmentionFs provides a complete toolkit for both sending and receiving webmentions, with built-in validation and URL discovery capabilities.

Built with modern F# best practices, leveraging discriminated unions, immutability, and type-safe error handling.

✨ Features

  • πŸ” URL Discovery: Automatic webmention endpoint discovery from HTML pages
  • πŸ“¨ Send Webmentions: Send webmentions to discovered endpoints
  • πŸ“₯ Receive Webmentions: Process incoming webmention requests
  • βœ… Request Validation: Validate webmention request format and content
  • 🏷️ Mention Classification: Identify different types of mentions (likes, replies, reposts, bookmarks)
  • πŸ”’ Security: Built-in validation to prevent spam and malicious requests
  • 🌐 Standards Compliant: Full compliance with W3C Webmention specification
  • 🎯 Modern F#: Idiomatic F# with discriminated unions, immutability, and explicit error handling
  • πŸ“š Well Documented: Comprehensive XML documentation and architectural guides

🎯 Target Framework

  • .NET Standard 2.1: Compatible with .NET Core 3.0+, .NET 5+, .NET 6+, and later
  • Modern F#: Uses latest F# language features (task CE, string interpolation, enhanced pattern matching) while maintaining .NET Standard 2.1 compatibility

πŸ“¦ Installation

Package Manager Console

Install-Package lqdev.WebmentionFs

.NET CLI

dotnet add package lqdev.WebmentionFs

PackageReference

<PackageReference Include="lqdev.WebmentionFs" Version="0.0.7" />

πŸš€ Quick Start

Basic Usage

open System
open WebmentionFs
open WebmentionFs.Services

// Create service instances
let discoveryService = new UrlDiscoveryService()
let senderService = new WebmentionSenderService(discoveryService)

// Define source and target URLs
let webmentionData = {
    Source = new Uri("https://your-blog.com/post-mentioning-target")
    Target = new Uri("https://target-site.com/post-being-mentioned")
}

// Send a webmention
let result = 
    senderService.SendAsync(webmentionData) 
    |> Async.AwaitTask 
    |> Async.RunSynchronously

match result with
| ValidationSuccess endpoint -> 
    printfn "Webmention sent successfully to: %s" endpoint.Endpoint.OriginalString
| ValidationError error -> 
    printfn "Failed to send webmention: %s" error

πŸ“– Detailed Usage

1. Sending Webmentions

WebmentionFs can automatically discover webmention endpoints and send mentions:

open WebmentionFs.Services

let discoveryService = new UrlDiscoveryService()
let senderService = new WebmentionSenderService(discoveryService)

let mentionData = {
    Source = new Uri("https://myblog.com/awesome-post")
    Target = new Uri("https://example.com/original-post")
}

async {
    let! result = senderService.SendAsync(mentionData) |> Async.AwaitTask
    
    match result with
    | ValidationSuccess data ->
        printfn "βœ… Webmention sent to endpoint: %s" data.Endpoint.OriginalString
        printfn "   Source: %s" data.RequestBody.Source.OriginalString
        printfn "   Target: %s" data.RequestBody.Target.OriginalString
    | ValidationError error ->
        printfn "❌ Error: %s" error
} |> Async.RunSynchronously

2. Receiving and Validating Webmentions

For processing incoming webmentions in an ASP.NET Core application:

open Microsoft.AspNetCore.Http
open WebmentionFs.Services

// Setup validation services
let hostList = [| "yourdomain.com"; "www.yourdomain.com" |]
let requestValidator = new RequestValidationService(hostList)
let webmentionValidator = new WebmentionValidationService()
let receiverService = new WebmentionReceiverService(requestValidator, webmentionValidator)

// In your controller/handler
let processWebmention (httpRequest: HttpRequest) = async {
    let! result = receiverService.ReceiveAsync(httpRequest) |> Async.AwaitTask
    
    match result with
    | ValidationSuccess webmention ->
        printfn "βœ… Valid webmention received!"
        printfn "   Source: %s" webmention.RequestBody.Source.OriginalString
        printfn "   Target: %s" webmention.RequestBody.Target.OriginalString
        
        // Check mention types
        if webmention.Mentions.IsLike then printfn "   Type: Like ❀️"
        elif webmention.Mentions.IsReply then printfn "   Type: Reply πŸ’¬"
        elif webmention.Mentions.IsRepost then printfn "   Type: Repost πŸ”„"
        elif webmention.Mentions.IsBookmark then printfn "   Type: Bookmark πŸ”–"
        else printfn "   Type: Generic mention"
        
        // Process the webmention (save to database, send notifications, etc.)
        
    | ValidationError error ->
        printfn "❌ Invalid webmention: %s" error
}

3. URL Discovery

Discover webmention endpoints from a target URL:

let discoveryService = new UrlDiscoveryService()

let urlData = {
    Source = new Uri("https://myblog.com/post")
    Target = new Uri("https://target.com/post")
}

async {
    let! result = discoveryService.DiscoverEndpointAsync(urlData) |> Async.AwaitTask
    
    match result with
    | DiscoverySuccess endpoint ->
        printfn "Found webmention endpoint: %s" endpoint.Endpoint.OriginalString
    | DiscoveryError error ->
        printfn "Could not discover endpoint: %s" error
} |> Async.RunSynchronously

4. Manual Request Validation

Validate webmention requests without processing them:

let hostList = [| "mydomain.com" |]
let validator = new RequestValidationService(hostList)

async {
    let! result = validator.ValidateAsync(webmentionData) |> Async.AwaitTask
    
    match result with
    | RequestSuccess data ->
        printfn "βœ… Request is valid"
        printfn "   Source: %s" data.Source.OriginalString
        printfn "   Target: %s" data.Target.OriginalString
    | RequestError error ->
        printfn "❌ Request validation failed: %s" error
} |> Async.RunSynchronously

πŸ—οΈ Architecture

Core Types

WebmentionFs uses a functional approach with discriminated unions for result handling:

// Basic URL data
type UrlData = {
    Source: Uri
    Target: Uri
}

// Mention classification
type MentionTypes = {
    IsBookmark: bool
    IsLike: bool
    IsReply: bool
    IsRepost: bool
}

// Result types
type ValidationResult<'a> = 
    | ValidationSuccess of 'a
    | ValidationError of string

type DiscoveryResult = 
    | DiscoverySuccess of EndpointUrlData
    | DiscoveryError of string

Service Overview

  • UrlDiscoveryService: Discovers webmention endpoints using multiple methods (HTTP headers, <link> tags, <a> tags)
  • WebmentionSenderService: Combines discovery and sending functionality
  • RequestValidationService: Validates incoming webmention requests
  • WebmentionValidationService: Analyzes source documents for mention types
  • WebmentionReceiverService: Complete pipeline for processing incoming webmentions

πŸ”§ Configuration

Request Validation

Configure which domains you own for validation:

let myDomains = [| 
    "myblog.com"
    "www.myblog.com" 
    "staging.myblog.com"
|]

let validator = new RequestValidationService(myDomains)

ASP.NET Core Integration

Register services in your DI container:

// In Startup.fs or Program.fs
services.AddSingleton<UrlDiscoveryService>() |> ignore
services.AddSingleton<WebmentionValidationService>() |> ignore
services.AddSingleton<RequestValidationService>(fun _ -> 
    new RequestValidationService([| "yourdomain.com" |])) |> ignore
services.AddTransient<WebmentionSenderService>() |> ignore
services.AddTransient<WebmentionReceiverService>() |> ignore

πŸ” Endpoint Discovery

WebmentionFs discovers webmention endpoints using the standard methods defined in the specification:

  1. HTTP Link Header: Link: <https://example.com/webmention>; rel="webmention"
  2. HTML Link Element: <link rel="webmention" href="https://example.com/webmention">
  3. HTML Anchor Element: <a rel="webmention" href="https://example.com/webmention">webmention</a>

The library tries all methods and uses the first successful discovery.

🏷️ Mention Types

WebmentionFs can automatically classify mentions based on microformat annotations:

  • Likes: Elements with class u-like-of
  • Replies: Elements with class u-in-reply-to
  • Reposts: Elements with class u-repost-of
  • Bookmarks: Elements with class u-bookmark-of
  • Generic: Any other link to the target URL

πŸ›‘οΈ Security Features

  • Protocol validation: Only HTTP/HTTPS URLs are accepted
  • Domain ownership verification: Ensures targets belong to configured domains
  • URL validation: Verifies target URLs are accessible
  • Content type checking: Validates HTML content types
  • Same URL prevention: Prevents self-referential webmentions

πŸ› Error Handling

All operations return discriminated unions for explicit error handling:

match result with
| ValidationSuccess data -> 
    // Handle success case
    processWebmention data
| ValidationError error ->
    // Handle error case
    logError error
    respondWithError 400 error

Common error scenarios:

  • Invalid URL formats
  • Target URL not accessible
  • No webmention endpoint found
  • Network connectivity issues
  • Invalid request format

πŸ—οΈ Architecture & Design

WebmentionFs is built using modern F# idioms and best practices:

  • Discriminated Unions: All result types use discriminated unions for explicit, type-safe error handling
  • Immutability: All domain types are immutable records
  • Function Composition: Validation pipelines use function composition for clarity
  • Explicit Errors: No hidden exceptions - all failures are represented in return types
  • Modern Async: Uses task {} computation expressions for async operations
  • Type Safety: Leverages F#'s type system to prevent invalid states

For detailed architectural information, see ARCHITECTURE.md.

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Documentation for Contributors

Project Structure

The repository follows standard F# project organization:

WebmentionFs/
β”œβ”€β”€ src/
β”‚   └── WebmentionFs/          # Main library source code
β”‚       β”œβ”€β”€ Domain.fs           # Core types and domain models
β”‚       β”œβ”€β”€ Constants.fs        # Constants and configuration
β”‚       β”œβ”€β”€ Utils.fs            # Utility functions
β”‚       β”œβ”€β”€ UrlDiscoveryService.fs
β”‚       β”œβ”€β”€ RequestValidationService.fs
β”‚       β”œβ”€β”€ WebmentionValidationService.fs
β”‚       β”œβ”€β”€ WebmentionReceiverService.fs
β”‚       β”œβ”€β”€ WebmentionSenderService.fs
β”‚       └── WebmentionFs.fsproj
β”œβ”€β”€ tests/
β”‚   └── WebmentionFs.Tests/    # XUnit test project
β”‚       β”œβ”€β”€ Tests.fs            # Unit tests
β”‚       └── WebmentionFs.Tests.fsproj
β”œβ”€β”€ WebmentionFs.slnx          # Solution file (XML format)
└── README.md

Development Setup

  1. Clone the repository:

    git clone https://github.com/lqdev/WebmentionFs.git
    cd WebmentionFs
  2. Build the entire solution:

    dotnet build WebmentionFs.slnx
  3. Run the tests:

    dotnet test WebmentionFs.slnx

    Or run tests directly from the test project:

    dotnet test tests/WebmentionFs.Tests/WebmentionFs.Tests.fsproj
  4. Build only the main library:

    dotnet build src/WebmentionFs/WebmentionFs.fsproj

Running Tests

The project uses XUnit as the testing framework. Tests are located in the tests/WebmentionFs.Tests/ directory.

To run all tests:

dotnet test

To run tests with detailed output:

dotnet test --verbosity normal

To run tests for a specific project:

dotnet test tests/WebmentionFs.Tests/WebmentionFs.Tests.fsproj

F# Conventions

This project follows modern F# best practices:

  • camelCase for values and functions
  • PascalCase for types and modules
  • Discriminated unions for state representation
  • Explicit error handling with Result types
  • Pure functions and immutable data
  • task {} for async operations

See CONTRIBUTING.md for detailed guidelines.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

πŸ“š Additional Resources


Author: Luis Quintanilla
Repository: https://github.com/lqdev/WebmentionFs
NuGet Package: https://www.nuget.org/packages/lqdev.WebmentionFs/

About

F# library for processing webmentions

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages