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.
- π 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
- .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
Install-Package lqdev.WebmentionFsdotnet add package lqdev.WebmentionFs<PackageReference Include="lqdev.WebmentionFs" Version="0.0.7" />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" errorWebmentionFs 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.RunSynchronouslyFor 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
}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.RunSynchronouslyValidate 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.RunSynchronouslyWebmentionFs 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 stringUrlDiscoveryService: Discovers webmention endpoints using multiple methods (HTTP headers,<link>tags,<a>tags)WebmentionSenderService: Combines discovery and sending functionalityRequestValidationService: Validates incoming webmention requestsWebmentionValidationService: Analyzes source documents for mention typesWebmentionReceiverService: Complete pipeline for processing incoming webmentions
Configure which domains you own for validation:
let myDomains = [|
"myblog.com"
"www.myblog.com"
"staging.myblog.com"
|]
let validator = new RequestValidationService(myDomains)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>() |> ignoreWebmentionFs discovers webmention endpoints using the standard methods defined in the specification:
- HTTP Link Header:
Link: <https://example.com/webmention>; rel="webmention" - HTML Link Element:
<link rel="webmention" href="https://example.com/webmention"> - HTML Anchor Element:
<a rel="webmention" href="https://example.com/webmention">webmention</a>
The library tries all methods and uses the first successful discovery.
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
- 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
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 errorCommon error scenarios:
- Invalid URL formats
- Target URL not accessible
- No webmention endpoint found
- Network connectivity issues
- Invalid request format
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.
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.
- CONTRIBUTING.md: F# coding conventions, development workflow, testing guidelines
- ARCHITECTURE.md: Design decisions, module relationships, extension points
- AI_ASSISTANT_GUIDE.md: Guide for AI coding assistants working with this codebase
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
-
Clone the repository:
git clone https://github.com/lqdev/WebmentionFs.git cd WebmentionFs -
Build the entire solution:
dotnet build WebmentionFs.slnx
-
Run the tests:
dotnet test WebmentionFs.slnxOr run tests directly from the test project:
dotnet test tests/WebmentionFs.Tests/WebmentionFs.Tests.fsproj -
Build only the main library:
dotnet build src/WebmentionFs/WebmentionFs.fsproj
The project uses XUnit as the testing framework. Tests are located in the tests/WebmentionFs.Tests/ directory.
To run all tests:
dotnet testTo run tests with detailed output:
dotnet test --verbosity normalTo run tests for a specific project:
dotnet test tests/WebmentionFs.Tests/WebmentionFs.Tests.fsprojThis 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.
This project is licensed under the MIT License - see the LICENSE file for details.
- W3C Webmention Specification
- Webmention.rocks for testing endpoints
- The IndieWeb community for webmention advocacy
- Project Documentation
- Webmention Resources
- F# Resources
- Development Tools
Author: Luis Quintanilla
Repository: https://github.com/lqdev/WebmentionFs
NuGet Package: https://www.nuget.org/packages/lqdev.WebmentionFs/